浏览器本身也可以看成一个能够联网的客户端程序。当我们在浏览器里访问:
text
http://127.0.0.1:8080
浏览器会主动和本机的 8080 端口建立连接,然后按照 HTTP 协议发送一段数据。所以这次服务端仍然从最熟悉的:
java
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
等待浏览器访问。浏览器连进来以后,通过:
java
InputStream in = socket.getInputStream();
就能够读取浏览器发送过来的数据。
刚开始把这些内容直接打印出来,可以看到类似:
text
GET / HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: ...
Accept: ...
Connection: ...
这些内容其实就是 HTTP 请求。
前面自己写聊天室时,消息格式是自己规定的,比如:
text
消息类型 + 消息长度 + 消息内容
现在浏览器和服务器之间同样有协议,只不过这套协议已经被规定好了,也就是 HTTP。所以从这一点来看,HTTP 和前面自己设计的消息协议思路并没有本质区别,都是规定"这一段数据中,每一部分分别是什么意思"。
文章目录
-
- 一、先写一个简单的登录页面
- [二、点击按钮后怎么把数据发给 Java](#二、点击按钮后怎么把数据发给 Java)
- [三、Java 服务端解析 HTTP 请求](#三、Java 服务端解析 HTTP 请求)
- [四、服务器还要按照 HTTP 格式返回结果](#四、服务器还要按照 HTTP 格式返回结果)
- 五、前端接收后端返回的数据
一、先写一个简单的登录页面
网站前端主要涉及三部分:
text
HTML → 页面上有什么
CSS → 页面长什么样
JavaScript → 页面能做什么
这次先做一个最简单的登录页面。HTML 主要负责页面结构,例如:
html
账号:
<input id="username" type="text"/>
密码:
<input id="pwd" type="password"/>
<input onclick="hello()"
type="button"
value="登录"/>
这里用了三个 input。
- 第一个用来输入账号。用来输入账号。
html
type="text"
- 第二个用来输入密码。
html
type="password"
- 第三个就是登录按钮。
html
type="button"
为了后面 JavaScript 能找到账号和密码输入框,还给它们分别加了:id="username"和: id="pwd"
页面中还设置了:
html
<meta charset="utf-8">
主要是为了正常显示中文。CSS 这部分暂时比较简单:
css
.hello {
background-color: red;
}
然后:
html
<div class="hello">
就可以把这一块应用对应的样式。
二、点击按钮后怎么把数据发给 Java
如果页面只有 HTML 和 CSS,那么点击按钮以后实际上不会完成登录,真正负责交互的是 JavaScript。登录按钮:
html
<input onclick="hello()"
type="button"
value="登录"/>
点击以后会调用hello()这个函数。
首先获取用户输入的账号和密码:
javascript
var username =
document
.getElementById("username")
.value;
var pwd =
document
.getElementById("pwd")
.value;
这里的:
javascript
document.getElementById()
就是根据前面设置的 id 找到页面中的组件。拿到数据以后,这次使用了一个比较原始的网络请求方式:
javascript
var xmlrequest = new XMLHttpRequest();
然后打开一个 GET 请求:
javascript
xmlrequest.open("GET","http://127.0.0.1:8080/api/login?username=" + username + "&pwd=" + pwd,true);
假设输入:
text
账号:admin
密码:123456
最终浏览器访问的就是:
text
http://127.0.0.1:8080/api/login?username=admin&pwd=123456
/api/login 表示请求哪个功能。
? 表示开始携带参数。
username=admin 表示一个参数。
& 多个参数之间使用分隔。
javascript
xmlrequest.send();
才真正把请求发送出去。
三、Java 服务端解析 HTTP 请求
浏览器发出请求以后,Java 服务端收到的第一行是:
text
GET /api/login?username=admin&pwd=123456 HTTP/1.1
所以服务端第一步使用:
java
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String requestLine = reader.readLine();
读取 HTTP 请求的第一行,这一行里面已经包含了这次登录需要的大部分信息,先按照空格拆:
java
String[] parts = requestLine.split(" ");
可以得到:
text
GET
/api/login?username=admin&pwd=123456
HTTP/1.1
其中:
java
String method = parts[0];
String fullPath = parts[1];
就可以分别拿到请求方式和请求地址。
接下来再寻找*?*的位置:
java
int idx = fullPath.indexOf('?');
把路径和参数分开,最终得到:
text
path = /api/login
query = username=admin&pwd=123456
然后再按照*&*拆开参数:
java
for (String param : query.split("&")) {
每一组再按照=拆:
java
String[] kv = param.split("=");
最后就能够拿到:
text
username = admin
pwd = 123456
现在这个版本暂时直接在服务端写死账号密码:
java
static final String VALID_USER = "admin";
static final String VALID_PWD = "123456";
然后判断:
java
boolean success = VALID_USER.equals(username) && VALID_PWD.equals(password);
账号密码为admin / 123456则登录成功,其他账号密码则失败。
四、服务器还要按照 HTTP 格式返回结果
刚开始调试时有一个比较明显的问题:虽然服务器已经完成了账号密码判断,但是浏览器迟迟拿不到结果。
这里和前面写 Socket 聊天不一样,聊天室里可以直接:os.write(...)发送一句文字。
但是浏览器希望收到的是一条符合 HTTP 协议的响应。例如登录成功时,现在代码返回:
text
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: *
Content-Length: ...
登录成功~
代码中实际是:
java
String response = "HTTP/1.1 " + statusCode + " " + statusText + "\r\n" +
"Content-Type: text/html; charset=utf-8\r\n" +
"Access-Control-Allow-Origin: *\r\n" +
"Content-Length: " + body.getBytes().length + "\r\n" +
"\r\n" + // 空行分隔头部和正文
body;
这里有一个比较重要的格式:
text
响应状态行
响应头
空行
响应正文
中间必须有空行,把头部和真正返回的数据分开。
- 登录成功时使用:
text
200 OK
- 登录失败则使用:
text
401 Unauthorized
- 如果路径不存在:
text
404 Not Found
- 请求方法不支持:
text
405 Method Not Allowed
所以浏览器不仅能收到:登录成功~,还能够知道本次 HTTP 请求到底成功还是失败。
五、前端接收后端返回的数据
JavaScript 发出请求以后,还需要监听服务器什么时候返回,页面中使用:
javascript
xmlrequest.onreadystatechange =
function () {
if (
xmlrequest.readyState === 4
) {
var data =
xmlrequest.responseText;
console.log(
"结果返回:" + data
);
alert(data);
}
};
其中:readyState === 4,表示这次请求已经完成。然后通过:responseText,得到 Java 服务端返回的正文。
例如服务器返回:
text
登录成功~
前端最后会通过:
javascript
alert(data);
弹窗显示。
到这里,整个流程就是:
text
用户填写账号密码
↓
点击登录
↓
JavaScript获取输入框
↓
XMLHttpRequest发送GET请求
↓
Java ServerSocket收到请求
↓
解析HTTP请求行
↓
取出账号密码
↓
执行登录判断
↓
返回HTTP响应
↓
JavaScript读取responseText
↓
页面显示结果
也就是:
text
前端 → 网络 → 后端 → 网络 → 前端