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