网站通信(一)

网站通信(一)

浏览器本身也可以看成一个能够联网的客户端程序。当我们在浏览器里访问:

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 ↓ 页面显示结果

也就是:

前端 → 网络 → 后端 → 网络 → 前端