美文网首页
Java----使用nioSocket获取和返回http报文

Java----使用nioSocket获取和返回http报文

作者: 不过意局bugyj | 来源:发表于2018-09-28 16:23 被阅读0次

参考博客: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请求,返回报文下面实现!

NioSocket的简单使用

在浏览器中输入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文件内容也可!

相关文章

网友评论

      本文标题:Java----使用nioSocket获取和返回http报文

      本文链接:https://www.haomeiwen.com/subject/eaepoftx.html