zoukankan      html  css  js  c++  java
  • HTTP 协议

    HTTP 协议构建于 TCP/IP 协议之上,是一个应用层协议。

    维基百科:https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE

    HTTP 协议头信息必须是 ASCII 码,后面的数据可以是任何格式(服务器回应的时候,必须告诉客户端,数据是什么格式,Content-Type 字段的作用)。

    https://developer.mozilla.org/zh-CN/docs/Web/HTTP

    规范把 HTTP 请求分为三个部分:请求行(Request Line)、请求头(Headers)、消息主体(Body)。

    其中,headers 和 body 是可选的。

    Request Line 和 Headers 以 分隔,Headers 与 Body 之间有一个空行(两个 )。

    <method> <request-URL> <version>
    <headers>
    
    <body>

    method 有很多多,常见的就是 GET 和 POST。http 1.1 为了表达不同的语义,引入了诸如 DELETE、PATCH 等方法,这种语义不是强制性的。

    协议并没有规定 GET 请求不能带 body 。理论上,任何 HTTP 请求都可以带 body 的。代理一般不会缓存 POST 请求的响应内容。

    HTTP 请求例子:

    GET / HTTP/1.1
    Host: www.baidu.com
    Connection: keep-alive
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
    Accept: */*

    服务器响应

    HTTP/1.1 200 OK 
    Content-Type: text/plain
    Content-Length: 137582
    Expires: Thu, 05 Dec 1997 16:00:00 GMT
    Last-Modified: Wed, 5 August 1996 15:55:28 GMT
    Server: Apache 0.84
    
    <html>
      <body>Hello World</body>
    </html>

    GET 产生一个 TCP 数据包,POST 产生两个TCP 数据包。

    对于 GET 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应200(返回数据)。

    对于 POST 方式的请求,浏览器先发送 header,服务器响应 100(continue),然后再发送 data,服务器响应200(返回数据)。

    Java 实现的简单 HTTP 服务

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.nio.charset.Charset;
    import java.util.Iterator;
    
    public class MyHttpServer {
        // 事件轮询器
        private static Selector selector;
        // 服务通道
        private static ServerSocketChannel serverSocketChannel;
    
        public static void main(String[] args) throws Exception {
            // 创建 ServerSocketChannel
            serverSocketChannel = ServerSocketChannel.open();
            // 监听 80 端口
            serverSocketChannel.socket().bind(new InetSocketAddress(80));
            // 设置为非阻塞模式
            serverSocketChannel.configureBlocking(false);
            // 为 ssc 注册选择器
            selector = Selector.open();
            /**
             * 将通道注册到选择器上, 并且指定“监听接收事件”
             * SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
             * SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功
             * SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了)
             * SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作)
             */
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            // 创建处理器
            while (true) {
                // 等待请求,每次等待阻塞 3s,超过 3s 后线程继续向下运行,如果传入 0 或者不传参数将一直阻塞
                if (selector.select(3000) == 0) {
                    continue;
                }
                // 获取待处理的SelectionKey
                Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
    
                while (keyIter.hasNext()) {
                    SelectionKey key = keyIter.next();
                    // 从待处理的 SelectionKey 迭代器中移除当前所使用的 key
                    keyIter.remove();
                    // 启动新线程处理 SelectionKey
                    new Thread(new HttpHandler(key)).run();
                }
            }
        }
    
        private static class HttpHandler implements Runnable {
            private int bufferSize = 1024;
            private String localCharset = "UTF-8";
            private SelectionKey key;
    
            public HttpHandler(SelectionKey key) {
                this.key = key;
            }
    
            public void handleAccept() throws IOException {
                serverSocketChannel = (ServerSocketChannel) key.channel();
                // 建立和客户端的链接, 因为 OP_ACCEPT 是注册在 serverSocketChannel上,每个客户又有自己的 SocketChannel 通道
                SocketChannel clientChannel = serverSocketChannel.accept();
                clientChannel.configureBlocking(false);
                // 开启注册该客户端的可读时间,即该客户上传完所有的请求数据到系统 kernel 的 buffer 缓存中后,开启可读事件通知
                clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
            }
    
            public void handleRead() throws IOException {
                // 获取 channel
                SocketChannel sc = (SocketChannel) key.channel();
                sc.configureBlocking(false);
                // 获取 buffer 并重置
                ByteBuffer buffer = (ByteBuffer) key.attachment();
                buffer.clear();
                // 没有读到内容则关闭
                if (sc.read(buffer) == -1) {
                    sc.close();
                } else {
                    // 接收请求数据
                    buffer.flip();
                    String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
                    // 控制台打印请求报文头
                    String[] requestMessage = receivedString.split("
    ");
                    for (String s : requestMessage) {
                        System.out.println(s);
                        // 遇到空行说明报文头已经打印完
                        if (s.isEmpty()) {
                            break;
                        }
                    }
                    // 根据上面的处理情况,再注册这里的信息
                    sc.register(key.selector(), SelectionKey.OP_WRITE, requestMessage);
                }
            }
    
            public void handleWrite() throws IOException {
                SocketChannel sc = (SocketChannel) key.channel();
                sc.configureBlocking(false);
                String[] requestMessage = (String[]) key.attachment();
    
                // 返回客户端
                StringBuilder sendString = new StringBuilder();
                sendString.append("HTTP/1.1 200 OK
    "); // 响应报文首行,200 表示处理成功
                sendString.append("Content-Type:text/html;charset=" + localCharset + "
    ");
                sendString.append("
    "); // 报文头结束后加一个空行
                sendString.append("<html><head><title>显示报文</title></head><body>");
                sendString.append("接收到请求报文是:<br/>");
                for (String s : requestMessage) {
                    sendString.append(s + "<br/>");
                }
                sendString.append("</body></html>");
                ByteBuffer buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));
                sc.write(buffer);
                sc.close();
    
                // 根据上面的处理情况,再注册这里的信息
                // sc.register(key.selector(), SelectionKey.OP_CONNECT);
            }
    
            @Override
            public void run() {
                try {
                    if (key.isAcceptable()) {
                        // 接收到连接请求时
                        handleAccept();
                    } else if (key.isReadable()) {
                        // 读数据
                        handleRead();
                    } else if (key.isWritable()) {
                        // 写数据
                        handleWrite();
                    } else if (key.isConnectable()) {
                        System.out.println("isConnectable============");
                    } else if (key.isValid()) {
                        System.out.println("isValid==================");
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    https://www.ruanyifeng.com/blog/2016/08/http.html

    https://hit-alibaba.github.io/interview/basic/network/HTTP.html

    https://www.zhihu.com/question/28586791

    https://blog.csdn.net/wangjun5159/article/details/47781443

  • 相关阅读:
    “Computer Management Snapin Launcher已停止工作”的解决方案
    IFrame与window对象(contentWindow)
    使用Emeditor转换编码(ShiftJS 到 UTF8)
    从注册表中删除程序,不要忘记这两个地方
    Visual Studio fatal error C1902: 程序数据库管理器不匹配;请检查安装
    一些TC内置的环境环境变量(注意字母必须大写,且只能在TC内用)
    使用WIN32 API CreateProcess()以无窗口方式创建DOS程序
    VC中DDX/DDV自定义
    javascript 一条语句实现随机数语句
    Emeditor
  • 原文地址:https://www.cnblogs.com/jhxxb/p/11679606.html
Copyright © 2011-2022 走看看