NIO學習三、基於NIO的WEB伺服器

大多多發表於2017-12-05

本文主要是對於NIO的應用,沒什麼特別的地方。

一、準備過程

     實現的http伺服器只可以訪問靜態資源,需要將檔案放在webroot目錄下。

二、設計流程:

    1、開發Request進行請求資源的解析,找到請求的路徑,如果請求不合法丟擲異常。
    2、開發Response將資源返回給客戶端
    3、開發HttpServer,建立ServerSocketChannel,獲得客戶端的SocketChannel進行處理。並將產生異常的請求關閉。

三、具體程式碼:

HttpServer:
  package com.webserver;


import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class HttpServer {
    protected static final String WEB_ROOT=System.getProperty("user.dir")+ File.separator+"webroot";
    protected  static final  int PORT=8080;
    protected  static final  String HOST="127.0.0.1";
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    public  static  void main(String args[]){
        HttpServer httpServer=new HttpServer();
        httpServer.run();
    }
    public void run() {
        //開始執行先建立伺服器
        if (serverSocketChannel==null) {
            System.out.println("伺服器啟動。。。。");
            createServer();
        }
        //一直等待建立連線
        while(true) {
            try {
                this.selector.select();//這地方會阻塞等待建立連線
                Set<SelectionKey> sets=this.selector.selectedKeys();
                Iterator<SelectionKey> iterator=sets.iterator();
                ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
                while(iterator.hasNext()){
                    SelectionKey selectionKey=iterator.next();
                    //處理感興趣的事件
                    dohandelInteresting(selectionKey);
                    iterator.remove();
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
                continue;
            }
        }
    }

    private void dohandelInteresting(SelectionKey selectionKey)throws Exception{
        if (selectionKey.isAcceptable()){//如果有連線接入那麼進行處理

                doHandleLink();//處理連線
        }else if (selectionKey.isReadable()) {//希望請求資料
                SocketChannel socketChannel= (SocketChannel) selectionKey.channel();
            try {
                //處理請求,處理請求的時候可能會發生的異常:請求出錯或者
                //遠端客戶端關閉連線,但服務端還進行操作,這時候統統關閉服務端連線
                doHandleAccess(socketChannel);
            } catch (Exception e) {
                //如果處理請求的過程中發生異常就由服務端取消註冊並且關閉它
                socketChannel.close();
                selectionKey.cancel();
                throw  e;
            }
        }
    }
    private void doHandleLink() throws IOException {
        SocketChannel socketChannel=this.serverSocketChannel.accept();

            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
    }
    //處理請求分為兩步,(1)解析請求資源,(2)回送給客戶端
    private void doHandleAccess(SocketChannel socketChannel) throws Exception {
            //解析請求
            Request request = doHandleRequest(socketChannel);
            //回送客戶端
            doHandleReply(request, socketChannel);
    }

    private void doHandleReply(Request request, SocketChannel socketChannel) throws Exception {
        Response response=new Response(socketChannel,request);
        response.sendStaticResource();
    }

    //處理到來的request請求
    private Request  doHandleRequest(SocketChannel socketChannel) throws Exception {
        Request request=new Request(socketChannel);
        request.doHandelRequestContext();
        return  request;
    }
    //建立服務端
    private void createServer() {
        try {
            this.serverSocketChannel=ServerSocketChannel.open();
            this.serverSocketChannel.socket().bind(new InetSocketAddress(HOST,PORT));
            this.serverSocketChannel.configureBlocking(false);
            createSelector();
            this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void createSelector() throws IOException {
        this.selector=Selector.open();
    }
}  

Request:

package com.webserver;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

//接受請求,找出對應的url
public class Request {

    private String requestContext;
    private SocketChannel socketChannel;
    private String url;

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

    public String getRequestContext() {
        return requestContext;
    }
    public String getUrl() {
        return url;
    }
    void doHandelRequestContext() throws Exception{
        paserRequestContext();
        paserRequestUrl();
    }

    private void paserRequestContext() throws Exception {
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        byteBuffer.clear();
        byte[]temp=new byte[1024];
        int length=0;
        StringBuilder stringBuilder=new StringBuilder();
        while ((length = socketChannel.read(byteBuffer)) > 0) {
            stringBuilder.append(new String(byteBuffer.array(), 0, length));
        }
        this.requestContext = stringBuilder.toString();
        if (this.requestContext.trim().equals("")) {
            throw new Exception("請求不合法");
        }
    }

    private void paserRequestUrl(){
        int index=requestContext.indexOf(" ");
        if (index!=-1){
           url=requestContext.substring(index+1,requestContext.indexOf(" ",index+1));
        }
        //預設請求/index.html
        if(url.equals("/")){
            url="/index.html";
        }
    }

}

Response

package com.webserver;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

public class Response {

    private SocketChannel socketChannel;
    private Request request;

    public Response(SocketChannel socketChannel){
        this(socketChannel,null);
    }
    public Response(SocketChannel socketChannel,Request request){
        this.request=request;
        this.socketChannel=socketChannel;
    }
    public void setRequest(Request request) {
        this.request = request;
    }

    public void sendStaticResource() throws Exception {

        FileInputStream fileInputStream=null;
        File file =null;
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        try {
            file= new File(HttpServer.WEB_ROOT, request.getUrl());
            if (file.exists()) {
                fileInputStream = new FileInputStream(file);
                FileChannel fileChannel=fileInputStream.getChannel();
                byteBuffer.put(("HTTP/1.1 200\r\n"
                        + "Content-Type: text/html\r\n"
                        + "Content-Length: "
                        + file.length()
                        + "\r\n"
                        + "\r\n").getBytes());
                byteBuffer.flip();
                socketChannel.write(byteBuffer);
                byteBuffer.clear();
                fileChannel.transferTo(0,fileChannel.size(),socketChannel);
            } else {
                String errorMessage = "HTTP/1.1 404 File Not Found\r\n"
                        + "Content-Type:text/html\r\n"
                        + "Content-Length:23\r\n"
                        + "\r\n"
                        + "<h1>File Not Found</h1>";
                byteBuffer.put(errorMessage.getBytes());
                byteBuffer.flip();
                socketChannel.write(byteBuffer);
                byteBuffer.clear();
                throw new Exception("沒找到指定檔案");
            }
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        finally {
            if(null!=fileInputStream){
                fileInputStream.close();
            }
        }
    }
}

四、訪問
訪問結果

ps:如有不對請指出~~~~

相關文章