在上一篇中,我们完成了web server的第一个项目搭建,完成了浏览器到我们服务器的通路,算是给两端搭上线了。但这实在太过于基础了。本期我们打算添加哪些内容呢?一起来看我们的本期目标:
1、按照http协议,解析浏览器发送过来的数据,顺带拿到请求参数;
2、根据浏览器请求路径的不同,返回不同的内容给浏览器。
走起,老铁们!
第一步,我们要从http报文中解析出相关的字段,拿到请求参数。先把上一节的服务器跑起来,然后回顾一下请求的报文格式和内容。我们平时工作中常用的http请求方法也就get和post这俩, 用的比较多。其他像put,delete,option啥的,用的就比较少了。我们就发一个最简单的get请求吧。为了方便调试,我们安装一下postman这个工具,用来模拟浏览器发送情趣,哦,不!发送请求给服务端。
服务端收到的内容:
我们要拿到GET——请求方法,/getUserById.do——请求url,HTTP/1.1——http版本(这个版本开始支持ka,也就是下面的connection:keep-alive,不详细展开了,大体就是一次请求结束后不释放连接,毕竟重建连接成本很高。)
我们先写一个解析http的通用方法:
/**
*
* @param httpContent
* @return
*/
public String parseHttpContent(String httpContent) {
/**简单处理了,不做格式校验了*/
if ("".equals(httpContent)) {
return null;
}
/**获取报文第一行*/
String firstRow = httpContent.split(Constant.CRLF)[0];
/**解析请求方法*/
String methodName = firstRow.split(Constant.BLANK)[0].trim().toLowerCase();
System.out.println("请求方法:"+methodName);
/**解析请求url*/
String requestUrl = firstRow.split(Constant.BLANK)[1].trim();
System.out.println("请求url:"+requestUrl);
/**解析请求协议版本*/
String protocolVersion = firstRow.split(Constant.BLANK)[2].trim();
System.out.println("请求协议版本:"+protocolVersion);
parseRequestParam(httpContent);
return null;
}
这样我们就拿到了http的请求头,请求url,请求协议的版本 这三个比较关键的字段
接下来就是拿到请求参数了,我们先看看常见的请求格式有哪几类吧。
GET http://localhost:8008/getUsers.do
GET http://localhost:8008/getUserByid.do?userId=123
GET http://localhost:8008/getUserByid.do?userId=123&areaId=345
POST http://localhost:8008/getUserByid.do
POST http://localhost:8008/addUser.do ,body参数 {"username":"lyn"}
POST http://localhost:8008/addUser.do ,body参数 {"username":"lyn","password":"lyn123"}
POST http://localhost:8008/addUser.do?username=lyn
POST http://localhost:8008/addUser.do?username=lyn&password=lyn123
POST http://localhost:8008/addUser.do?areaId=345 ,body参数 {"username":"lyn"}
POST http://localhost:8008/addUser.do?areaId=345 ,body参数 {"username":"lyn","password":"lyn123"}
POST http://localhost:8008/addUser.do?areaId=345&officeId=789 ,body参数 {"username":"lyn"}
POST http://localhost:8008/addUser.do?areaId=345&officeId=789 ,body参数 {"username":"lyn","password":"lyn123"}
乍一看可能比较多,其实我们真正关注的就这几个点:get还是post,请求中带不带“?”,参数有几个,请求体中有没有参数。上面就是这几种情况的组合。
具体到代码上:
/**
* 解析请求参数,暂只考虑get和post方式,参数封装为map,k-v格式,考虑一对多
* @param httpContent
*/
public void parseRequestParam(String httpContent) {
/**url后带问号的形式,/addUser.do?username=xx&pwd=xxx */
String firstRow = httpContent.split(Constant.CRLF)[0];
String methodName = firstRow.split(Constant.BLANK)[0].trim().toLowerCase();
String urlStr = firstRow.split(Constant.BLANK)[1];
/**根据请求体中是否包含参数来分类*/
String[] httpBodyArray = httpContent.split("\n\r\n");
if ("".equals(firstRow) || (!"post".equals(methodName) && !urlStr.contains("?")) ) return;
if (urlStr.contains("?")) {
/**格式为 key1=value1&key2=value2... */
String paramStr = urlStr.split("\\?")[1];
//只有一个参数
if (!urlStr.contains("&")) {
String paramKey = paramStr.split("=")[0];
String paramValue = paramStr.split("=")[1];
System.out.println("参数:"+paramKey+" , 值:"+paramValue);
}else { //多个参数
String[] paramArray = paramStr.split("&");
for (String param:paramArray) {
System.out.println("参数:"+param.split("=")[0]+" , 值:"+param.split("=")[1]);
}
}
}
/**如果请求体有参数,继续解析*/
if (httpBodyArray.length > 1) {
String bodyParamStr = httpBodyArray[1];
if (bodyParamStr.contains("&")) {
String[] bodyParamArray = bodyParamStr.split("&");
for (String bodyParam:bodyParamArray) {
System.out.println("Body参数: "+bodyParam.split("=")[0]+", Body值:"+bodyParam.split("=")[1]);
}
}else {
System.out.println("Body参数: "+bodyParamStr.split("=")[0]+", Body值:"+bodyParamStr.split("=")[1]);
}
}
}
我们来测试一下:
看下服务端:
这样,我们就顺利地从请求报文中拿到了常用的几个关键属性,比如请求方法,请求url,而且还拿到了请求参数。后面我们能做得事情就很多了。根据不同url执行不同业务方法返回不同内容。别慌,一步步来。
本期代码路径:https://github.com/justein/lyn-server/tree/master/server-parse,后面我们将在这个基础上实现参数的存储和灵活读取。我们下期再见!