天天看点

server java_简易 HTTP Server 实现(JAVA)

该简易的J2EE WEB容器缺失很多功能,却可以提供给大家学习HTTP容器大致流程。

注:容器功能很少,只供学习。

1. 支持静态内容与Servlet,不支持JSP

2. 仅支持304/404

3. 该设计参考Jetty容器

GIT地址:https://git.oschina.net/redcode/jerry.git

一、HTTP请求处理流程:

server java_简易 HTTP Server 实现(JAVA)

HTTP包的解析直接使用Socket读取InputStream,再根据HTTP协议读取HTTP请求头于数据体,HTTP GET请求头类似如下:

GET / HTTP/1.1

Accept: **

User-Agent:

Host:

Pragma: no-cache

Cookie:

Content-Length: 25

count=1&viewid=lNe3tRpyVj

请求头后换行,再封装POST请求数据:count=1&viewid0=lNe3tRpyVj

解析POST请求包时,读取请求头后再读取数据,存入Map中。检查请求类型如下:

//Request method check

if(!HttpMethod.isAccept(cmds[0])) {return null;

}

接受的请求类型枚举:

public enumHttpMethod {

GET,

POST;public static booleanisAccept(String method) {for(HttpMethod m : HttpMethod.values()) {if(m.name().equals(method)) {return true;

}

}return false;

}public staticHttpMethod getMethod(String method){for(HttpMethod m : HttpMethod.values()) {if(m.name().equals(method)) {returnm;

}

}return null;

}

}

POST 请求需读取 Content-Length 属性,即需要知道POST包中的参数包大小,当TCP包被拆分通过几条链路到达目的地时,根据包长度使得服务端能合理的等待数据到来。

//Read headers

String line;int contentLength = 0;

HashMap headers = new HashMap();while( (line = br.readLine()) != null

&& !line.equals("") ) {int idx = line.indexOf(": ");if(idx == -1) {continue;

}if(HttpHeaders.CONTENT_LENGTH.equals(line)) {

contentLength= Integer.parseInt(line.substring(idx+2).trim());

}

headers.put(line.substring(0, idx), line.substring(idx+2));

}

二、总体设计说明:

1.从Main函数开始说明应该的设计方法,有些机制可用于其他软件的设计。

部署结构如下:

%HOME%/lib

protected class ConnectorEndPoint extends SocketEndPoint implementsRunnable {public ConnectorEndPoint(Socket socket) throwsIOException {super(socket);

socket.setSoTimeout(7000);

}public voiddispatch() {

threadPool.dispatch(this);

}

@Overridepublic voidrun() {

......

}

}

3.HTTP包解析器

HTTP包解析类由org.mike.jerry.http.HttpRequestDecoder工作,HTTP请求处理都位于org.mike.jerry.http包中。

请求解析工作有几点:

1. 读取请求头,区分GET POST,获取请求头属性,GET读取URL中的符号“?”并解析参数,POST需要根据Content-Length再读取请求体中的请求参数。

把解析完成的数据存入Request中,根据Servlet设计规范,Request中需要存储请求体放入ServletInputStream in中,以供容器使用者在Servlet中能读取到InputStream.

2. 请求读取完毕后 把Resuqet交与 ResourceHandler 处理,读取所需要请求的资源。

4.读取资源

资源的读取中,默认请求为/的会固定读取/index.html文件,该属性本应该在web.xml中配置,不过为了学习简易,硬编码于此。

1. 首先检查这路径是否在Servlet中有匹配的,如果没有,则进行下一步。

2. 从webapps文件夹中读取请求的文件,如果不存在,则返回404,如果存在,则进行下一步。

3. 读取请求中的ETag码,这个标志类似于MD5、SHA1等文件摘要,用于标志文件是否改变,如果未改变,则返回304,节省服务器资源(CPU、磁盘与网络等)

,只是MD5与SHA1计算文件摘要需要的CPU周期较长,固计算方法修改如下:

publicString getWeakETag() {try{

StringBuilder b= new StringBuilder(32);

b.append("M/\"");int length=uri.length();long lhash=0;for (int i=0; i

lhash= 31*lhash +uri.charAt(i);

B64Code.encode(file.lastModified()^lhash, b);

B64Code.encode(length^lhash, b);

b.append('"');returnb.toString();

}catch(IOException e) {throw newRuntimeException(e);

}

}

5. 如果文件发生改变,则重新读取文件字节流,放入响应包Response中。

5. 响应HTTP包封装

5.1 响应头输出: 首先获取socket输出流,再写出头信息,127.0.0.1抓包工具可使用rawcap,得到pcap包后使用wireshark查看,格式类似于:

HTTP/1.1 200 OK

ETag: M/"AJMRnIhabgYAJMQ2H/NnL0"

Date: Wed, 5 Nov 2014 09:58:17 GMT

Content-Length: 1102

Last-Modified: Wed, 2 Jul 2014 23:01:08 GMT

Connection: Keep-Alive

Content-Type: text/html

Server: M

Cache-Control: private

相应代码如:

OutputStream out =socket.getOutputStream();//config status message

String respStat =HttpStatus.getMessage(response.getStatus());

StringBuilder headers= newStringBuilder();

headers.append(response.getHttpVersion()+ " "

+ response.getStatus() + " " + respStat +StringUtil.CRLF);//write headers

for(Map.Entryheader : response.getHeaders().entrySet()){

headers.append(header.getKey()+ ": " + header.getValue() +StringUtil.CRLF);

}

headers.append(StringUtil.CRLF);//响应头写入完毕必须空一行,这也是协议规定,以区分响应体

out.write(headers.toString().getBytes())

写入响应头后再写入响应体,也就是请求的资源内容。