编写自己的服务

通过前面相关的学习已经具备编写自己HTTP服务器的能力,不管是通过阻塞还是非阻塞的方式都可以实现。但是这里需要对HTTP协议进行一个了解。


HTTP协议简介

当用户打开浏览器,输入一个URL地址,就能收到远程HTTP服务器发送过来的网页。浏览器就是最常见的HTTP客户程序。

HTTP请求格式

HTTP协议规定,HTTP请求由3部分构成,分别是:

  • 请求方式、URI、HTTP协议的版本
  • 请求头
  • 请求正文

下面是一个HTTP请求的例子:

GET / HTTP/1.1
Host: www.google.com.hk
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
X-Chrome-UMA-Enabled: 1
X-Client-Data: CJS2yQEIpbbJAQjEtskBCOKYygEI+5zKAQipncoB
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: NID=98=EmnQgFsnopWExg1XEQNjPR1FKwTo1T7Qk5fH94bdjmUqIdJ6L9C_LLziCX8_UcDv_iyo84kOgKMPTnP0pbfuJqigpoxfDWouhyKX58J_gn2HU1abg7UJFik2bhwSHIU9kpJIEvQ6rtigHffscUqanx5_Tb-F1yq_4WiaBGjINA_A9siROY-WPTka8eRvElgyXk7koHQK

GET / HTTP/1.1 分别表示 请求方式(GET) URI(/) 协议版本(HTTP/1.1)
根据HTTP协议,HTTP请求可以使用多种方式:

  • GET:这种方式最为常见,客户程序可以通过这种方式访问服务器上的文档。
  • POST:客户程序可通过这种方式发送大量信息给服务器。例如HTML的表单提交。
  • HEAD:客户端和服务器之间交流一些内部书籍,服务器不会返回具体的文档。
  • PUT:客户程序通过这种方式把文档上传给服务器。
  • DELETE:客户程序通过这种方式删除服务器上的某个文档。

请求头:
请求头包含许多有关客户端环境和请求正文的有用信息。例如,请求头可以申明浏览器类型,所用的语言,请求正文的类型,已经请求正文的长度。

Host: www.google.com.hk
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
X-Chrome-UMA-Enabled: 1
X-Client-Data: CJS2yQEIpbbJAQjEtskBCOKYygEI+5zKAQipncoB
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: NID=98=EmnQgFsnopWExg1XEQNjPR1FKwTo1T7Qk5fH94bdjmUqIdJ6L9C_LLziCX8_UcDv_iyo84kOgKMPTnP0pbfuJqigpoxfDWouhyKX58J_gn2HU1abg7UJFik2bhwSHIU9kpJIEvQ6rtigHffscUqanx5_Tb-F1yq_4WiaBGjINA_A9siROY-WPTka8eRvElgyXk7koHQK

请求正文:

HTTP协议规定,请求头和请求正文之间必须以空行分给(只有CRLF[就是回车(CR, ASCII 13, \r) 换行(LF, ASCII 10, \n)。]符号的行),这个空行表示请求头已经结束,接下来是请求正文。下面是POST请求方式提交的表单数据

username=weixin&password=1234

HTTP响应格式

与HTTP请求相比,HTTP响应格式也由3部分构成:

  • HTTP协议版本、状态码、描述
  • 响应头(Response Header)
  • 响应正文(Response Content)

下面是一个HTTP响应的例子:

HTTP/1.1 200 OK
Date: Sun, 05 Mar 2017 04:31:31 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=UTF-8
Server: gws
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Alt-Svc: quic=":443"; ma=2592000; v="36,35,34"
Transfer-Encoding: chunked

HTTP协议的版本、状态码、描述

HTTP响应的第一行包括服务器使用的HTTP协议的版本,状态码、以及对状态的代码的描述。这三项以空格分开。HTTP/1.1 200 OK

状态码:

状态码是一个3位整数,以1、2、3、4或5开头。

  • 1XX :信息提示,表示临时的响应。
  • 2XX:响应成功,表示服务器成功接收了客户端的请求。
  • 3XX:重定向。
  • 4XX:客户端错误,表明客户端请求了不正确的资源或请求格式错误。
  • 5XX:服务器错误,表明服务器由于遇到某种错误而不能响应客户请求。

以下是一些常见的状态码:

  • 200:响应成功。
  • 400:错误的请求。客户发送的HTTP请求不正确。
  • 404:文件不存在。在服务端没有客户端请求的文档。
  • 405:服务器不支持客户端的请求方式。
  • 500:服务器内部错误。

响应头:
响应头也和请求头一样包含许多有用的信息。例如,服务器类型,正文类型。

Content-Type: text/html; charset=UTF-8
Server: gws

请求正文

在上面的响应格式中没有列出响应正文,因为是通过chrome查看的。chrome将响应正文放到另外的地方,因为响应正文一般都比较大。如下图

java 服务器请求url返回结果 java写http服务器_java 服务器请求url返回结果


通过HTTP响应头与响应正文之间必须用空行分隔。

创建一个简单的HTTP服务器:

通过ServerSocketChannelSocketChannelBuffer 以及线程池实现:

/**
 * 简单的HTTP服务器
 *
 * @author 在路上的coder
 * @create 2017-03-05 14:43
 **/
public class SimpleHttpServer {
    private int port = 80;
    private ServerSocketChannel serverSocketChannel;
    private ExecutorService executorService;
    private static final int POOL_SIZE = 4;
    private Charset charset = Charset.forName("UTF-8");

    public SimpleHttpServer() throws IOException {
        executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * POOL_SIZE);
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().setReuseAddress(true);
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        System.out.println("服务器启动成功");
    }

    public String decode(ByteBuffer byteBuffer) {
        return charset.decode(byteBuffer).toString();
    }

    public ByteBuffer encode(String string) {
        return charset.encode(string);
    }

    public void service() {
        while (true) {
            SocketChannel socketChannel = null;
            try {
                socketChannel = serverSocketChannel.accept();
                executorService.execute(new Handler(socketChannel));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    class Handler implements Runnable {

        private SocketChannel socketChannel;

        public Handler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }

        @Override
        public void run() {
            handle(serverSocketChannel);
        }

        private void handle(ServerSocketChannel serverSocketChannel) {
            try {
                Socket socket = socketChannel.socket();
                System.out.println("接收到客户链接,来自:" + socket.getInetAddress() + ":" + socket.getPort());

                ByteBuffer buffer = ByteBuffer.allocate(1024);
                socketChannel.read(buffer);//接收http请求,假定其长度不会超过1024个字节
                buffer.flip();//将limit的位置设为position,将position的值设置为0
                String request = decode(buffer);
                System.out.println("请求数据是:");
                System.out.println(request);
                System.out.println();

                //生成HTTP响应结果
                StringBuffer sb = new StringBuffer("HTTP/1.1 200 OK\r\n");
                sb.append("Content-Type:text/html\r\n\r\n");
                socketChannel.write(encode(sb.toString()));//发送HTTP响应的第一行和响应头

                FileInputStream in;
                //获取http请求的第一行
                String firstLineOfRequest = request.substring(0, request.indexOf("\r\n"));
                if (firstLineOfRequest.indexOf("login.html") != -1) {
                    in = new FileInputStream("E:\\application\\JetBrains\\workspace\\newWork\\nio\\src\\login.html");
                } else {
                    in = new FileInputStream("E:\\application\\JetBrains\\workspace\\newWork\\nio\\src\\hello.html");
                }
                FileChannel fileChannel = in.getChannel();
                fileChannel.transferTo(0,fileChannel.size(),socketChannel);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(socketChannel!=null){
                    try {
                        socketChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new SimpleHttpServer().service();
    }
}

访问方式
在浏览器输入 http://localhost/login.html
出现login页面,输入username和password。

在服务端控制台输出如下:

java 服务器请求url返回结果 java写http服务器_java_02

这个图的请求数据就是完整的包含:请求方式,URI、协议版本、请求头、请求正文。