手寫HTTP協議

我們知道HTTP協議是在應用層解析內容的,只需要按照它的報文的格式封裝和解析數據就可以了,具體的傳輸還是使用的Socket,在NioServer的基礎上自己做一個簡單的實現了HTTP協議的例子。
因爲HTTP協議是在接收到數據之後纔會用到的,所以我們只需要修改NioServer中的Handler就可以了,在修改後的HttpHandler中首先獲取到請求報文並打印出報文的頭部(包含首行)、請求的方法類型、Url和Http版本,最後將接收到的請求報文信息封裝到響應報文的主體中返回給客戶端。這裏的HttpHandler使用了單獨的線程來執行,而且把SelectionKey中操作類型的選擇也放在了HttpHandler中,不過具體處理過程和前面的NioServer沒有太大的區別,代碼如下:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

public class HttpServer {
    public static void main(String[] args) throws Exception{
        //創建ServerSocketChannel,監聽8080端口
        ServerSocketChannel ssc=ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(8080));
        //設置爲非阻塞模式
        ssc.configureBlocking(false);
        //爲ssc註冊選擇器
        Selector selector=Selector.open();
        ssc.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
                new Thread(new HttpHandler(key)).run();
                // 處理完後,從待處理的SelectionKey迭代器中移除當前所使用的key
                keyIter.remove();
            }
        }
    }

    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 {
            SocketChannel clientChannel=((ServerSocketChannel)key.channel()).accept();
            clientChannel.configureBlocking(false);
            clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
        }

        public void handleRead() throws IOException {
            // 獲取channel
            SocketChannel sc=(SocketChannel)key.channel();
            // 獲取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("\r\n");
                for(String s: requestMessage){
                    System.out.println(s);
                    // 遇到空行說明報文頭已經打印完
                    if(s.isEmpty())
                        break;
                }

                // 控制檯打印首行信息
                String[] firstLine = requestMessage[0].split(" ");
                System.out.println();
                System.out.println("Method:\t"+firstLine[0]);
                System.out.println("url:\t"+firstLine[1]);
                System.out.println("HTTP Version:\t"+firstLine[2]);
                System.out.println();

                // 返回客戶端
                StringBuilder sendString = new StringBuilder();
                sendString.append("HTTP/1.1 200 OK\r\n");//響應報文首行,200表示處理成功
                sendString.append("Content-Type:text/html;charset=" + localCharset+"\r\n");
                sendString.append("\r\n");// 報文頭結束後加一個空行

                sendString.append("<html><head><title>顯示報文</title></head><body>");
               sendString.append("接收到請求報文是:<br/>");
                for(String s: requestMessage){
                    sendString.append(s + "<br/>");
                }
                sendString.append("</body></html>");
                buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));
                sc.write(buffer);
                sc.close();
            }
        }

        @Override
        public void run() {
            try{
                // 接收到連接請求時
                if(key.isAcceptable()){
                    handleAccept();
                }
                // 讀數據
                if(key.isReadable()){
                    handleRead();
                }
            } catch(IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章