网站通信(一)

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

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 和前面自己设计的消息协议思路并没有本质区别,都是规定"这一段数据中,每一部分分别是什么意思"。


文章目录

一、先写一个简单的登录页面

网站前端主要涉及三部分:

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 复制代码
前端 → 网络 → 后端 → 网络 → 前端
相关推荐
Terra.K1 小时前
Java异常学习[特殊字符]
java·开发语言·学习
葡萄城技术团队3 小时前
InfluxDB 2\.x 深度解析:核心架构、Flux 函数与制造业落地指南(三)
java·开发语言·架构
Super 含3 小时前
Android 启动优化(五):线程、GC 与 IO 为什么会拖慢启动?
java·服务器·数据库
counting money3 小时前
Java IO流详解:从InputStream到文件操作实战
java·开发语言·python
程序员小八7774 小时前
上海百度B端java后端日常实习一面
java·开发语言
mldong5 小时前
零依赖的秘密:8 大 SPI 设计
java·架构
Diligently_7 小时前
Anolis 系统更新与密码设置&磁盘扩容
java·后端·spring
mqiqe7 小时前
线程调度与 Schedulers:Project Reactor 并发模型的核心引擎
java·开发语言·网络