参考博客:HTTP协议(一)之HTTP协议详解
HTTP协议
HTTP(HyperText Transfer Protocol)协议是运行在应用层的超文本传输协议。
书上举例:把网络请求比喻成发电报,我们将电报内容以摩尔斯电码的形式发出去,要先将内容翻译成摩尔斯电码,即编码。接受者接受到“滴滴滴”的信息,需要解码成人类能看得懂的内容。在网络传输过程中,http协议执行的功能就是编码解码操作。
HTTP报文
HTTP报文结构很重要的。可以分为请求报文(Request Message)和响应报文(Response Message)。两者在结构上大致相同,在内容上有很多不同。
每个报文的基本结构可分为三部分:首行,头部和主体,两者内容分别如下所示:
请求报文结构:
首行:有三个内容即请求方式、URL和HTTP版本\r\n
头部:采用键值对方式(用:隔开)描述相关属性,如请求编码等\r\n
\r\n
主体:是POST请求的参数
响应报文结构:
首行:状态码、HTTP版本和具体原因(可以没有)\r\n
头部:同上\r\n
\r\n
主体:网页要显示的内容
HTTP请求方式有 POST、GET、DELETE、HEAD和PUT。
状态码
状态码(status)标记的是网页响应状态结果:
状态码 | 类别 | 示例 |
---|---|---|
1XX | 信息性状态码 | 书上没有例子 |
2XX | 成功状态码 | 200表示请求成功 |
3XX | 重定向状态码 | 301表示请求重定向 |
4XX | 客户端错误状态码 | 404表示找不到请求的资源 |
5XX | 服务端错误状态码 | 500表示内部错误 |
http报文消息头
请求和相应报文的消息行下面都会有一连串的键值对似的消息头,平常的网络活动中,消息行不能表示所有信息,我们还需要额外的详细信息如日期、客户端支持的数据类型、语言、压缩格式、客户端和服务器所使用的软件名称、版本和数据有效期、版本号等。然后一行空行后就是消息体。
消息有的属性较多,写点常见的好理解的吧:
属性 | 作用 | 举例 |
---|---|---|
Cache-Control | 这个指令很重要,用来指定请求和响应的缓存机制 | 所有的属性值有:public(可以被任何缓存所缓存)、private(内容只缓存到私有缓存中)、no-cache(所有内容都不可被缓存) |
Accept | 指定浏览器可以接受的媒体类型,比如text/html就是接受我们常说的html文档,如果要接收jpg图片这里就可以指定image/jpg | Accept-Encoding:text/html |
Accept-Encoding | 指明浏览器接收的编码方式,通常指定的是数据压缩的方式,比如是否支持压缩,支持什么压缩 | Accept-Encoding: gzip, deflate, br |
Accept-Language | 指定浏览器所用语言,注意语言和字符集是有区别的,语言可能有很多字符集,比如中文是语言而中文有字符集UTF-8、gb2312、gbk等 | Accept-Language:zh-CN,zh;q=0.9,en-US;q=0.8,en-GB;q=0.7,en;q=0.6 |
User-Agent | 告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本.我们上网登陆论坛的时候,往往会看到一些欢迎信息,其中列出了你的操作系统的名称和版本,你所使用的浏览器的名称和版本,这往往让很多人感到很神奇,实际上,服务器应用程序就是从User-Agent这个请求报头域中获取到这些信息User-Agent请求报头域允许客户端将它的操作系统、浏览器和其它属性告诉服务器。 | User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36,具体含义都不是太明白,发现一篇文章写的很好,基本上有的内容都包含了! |
Accept-Charset | 浏览器声明自己使用的字符集 | Accept-charset="ISO-8859-1" |
Content-Length | 指明发送请求内容的长度 | Content-LEngth:38 |
Content-Type | 声明请求数据的类型 | Content-Type: application/x-www-form-urlencoded |
Connection | Connection: keep-alive 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接 | Connection: close 代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。 |
Host | 这个很必须,表示的是请求的服务器ip和端口信息,是从url中剥离出来的 | Host:localhost:8080 |
Location | 指明一个重定向到的url地址 | 存在于响应报文中,当状态码是304需要重定向时使用 |

http报文消息体
据我所知,消息体中一般存在如post所附属性,返回的html文档内容等!消息体在报文的空行下。
使用NioSocket获取报文信息并显示内容
了解到网页请求的形式是以包的形式发出,所遵循的协议是tcp,到达浏览器,内容由http所规范,以字节形式存在于数据包中,所以我们在代码中可以使用niosocket监听本机的8080端口(本来想监听80端口,可是已经被占用),然后获取其所包含的字节缓存到ByteBuffer中,然后转换成字符换,显示出来即可!
代码如下(在这里充当的是web服务器的角色,所以只写了服务器,请求由浏览器发出!):
package AboutNioSocket.ImplementTheHttpProtocolwWithNioSocket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
/**
* @author hsw
* @create 2018-09-27 19:54
*/
public class HttpServer {
private ServerSocketChannel channel = null;
private Selector selector = null;
public static void main(String[] args) {
new HttpServer().start();
}
private void start() {
try {
channel = ServerSocketChannel.open();
channel.configureBlocking(false);
} catch (IOException e) {
System.out.println("服务器创建失败 原因:ServerSocketChannel对象打开或设置非阻塞失败!");
e.printStackTrace();
}
try {
selector = Selector.open();
channel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
System.out.println("服务器创建失败 原因:Selector打开或注册失败");
e.printStackTrace();
}
try {
channel.bind(new InetSocketAddress(8080));
} catch (IOException e) {
System.out.println("服务器创建失败 原因:channel绑定端口失败!");
e.printStackTrace();
}
beginAccept();
}
private void beginAccept() {
while (true) {
try {
if (selector.select(3000) == 0) {
System.out.println("未接受到请求!");
continue;
}
} catch (IOException e) {
System.out.println("selector处理请求失败!");
e.printStackTrace();
}
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
new Thread(new HttpRequestHandler(key)).run();
}
}
}
}
HttpHandler类代码:
package AboutNioSocket.ImplementTheHttpProtocolwWithNioSocket;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
/**
* @author hsw
* @create 2018-09-27 20:20
*/
public class HttpRequestHandler implements Runnable {
private static final String CHARSET = "UTF-8";
private static final int BUFFER_SIZE = 1024;
private SelectionKey key;
public HttpRequestHandler(SelectionKey key) {
this.key = key;
}
private void acceptHttpRequest() {
SocketChannel socketChannel = null;
try {
socketChannel = ((ServerSocketChannel) key.channel()).accept();
socketChannel.configureBlocking(false);
} catch (IOException e) {
System.out.println("accept错误!");
e.printStackTrace();
}
try {
socketChannel.finishConnect();
} catch (IOException e) {
System.out.println("完成连接失败!");
e.printStackTrace();
}
Selector selector = key.selector();
try {
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(BUFFER_SIZE));
} catch (ClosedChannelException e) {
System.out.println("handler中注册selector失败!");
e.printStackTrace();
}
}
private String readHttpRequest() {
String httpStr = "";
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
byteBuffer.clear();
int num;
try {
if ((num = channel.read(byteBuffer)) != -1) {
byteBuffer.flip();
httpStr = Charset.forName(CHARSET).newDecoder().decode(byteBuffer).toString();
}
} catch (IOException e) {
System.out.println("服务器读取错误!");
e.printStackTrace();
}
return httpStr;
}
private String turnStringToHttpPackage(String httpPackageStr) {
return httpPackageStr;
}
//我们可以在这里将字符串转换成我们想要的格式!
private void returnHttpPackage(String returnHttpStr) {
System.out.println(returnHttpStr);
}
@Override
public void run() {
if (key.isAcceptable()) {
acceptHttpRequest();
} else if (key.isReadable()) {
returnHttpPackage(turnStringToHttpPackage(readHttpRequest()));
}
}
}
这里是接收并显示所获取的http请求,返回报文下面实现!
在浏览器中输入localhost:8080
即可在编译器中看到:

可以看到请求方法为GET,所以在Accept-Language的下一行为空行,而空行下面即请求体没有内容!
返回报文
我们只需修改returnHttpPackage方法中添加代码:
private void returnHttpPackage(String httpPackageStr) {
StringBuilder returnStr = new StringBuilder();
returnStr.append("HTTP/1.1 200 ok\r\n");//增加响应消息行
returnStr.append("Content-Type:text/html;charset=" + CHARSET + "\r\n");//增加响应消息头
returnStr.append("\r\n");//空行
returnStr.append("<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>返回的网页</title>\n" +
" <style>\n" +
" *{\n" +
" background-color: cadetblue;\n" +
" }\n" +
" #requestInfo{\n" +
" font-size: 35px;\n" +
" font-family: \"Buxton Sketch\",serif;\n" +
" font-weight: bold;\n" +
" color: #001bff;\n" +
" }\n" +
" </style>\n" +
"</head>\n" +
"<body>\n" +
" <h1>Hello!</h1><br>\n" +
" <h3>I'm come from localhost!</h3>\n" +
" <h3>And this is you request package information:</h3><br>\n" +
" <p id=\"requestInfo\">\n" +
httpPackageStr + //返回请求头信息!
" \n" +
" </p>\n" +
"</body>\n" +
"</html>");//响应消息体
System.out.println("返回内容:\n" + returnStr.toString());
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
buffer.clear();
try {
buffer.put(returnStr.toString().getBytes(CHARSET));
} catch (UnsupportedEncodingException e) {
System.out.println("返回内容字节转换失败!");
e.printStackTrace();
}
try {
buffer.flip();
channel.write(buffer);
} catch (IOException e) {
System.out.println("报文返回失败!");
e.printStackTrace();
}
try {
channel.close();
} catch (IOException e) {
System.out.println("channel关闭错误!");
e.printStackTrace();
}
}
再次请求吗,显示效果如下:

或者可以直接在网页中复制html文件内容也可!
网友评论