1. 程式人生 > >Mina、Netty、Twisted一起學(八):HTTP伺服器

Mina、Netty、Twisted一起學(八):HTTP伺服器

HTTP協議應該是目前使用最多的應用層協議了,用瀏覽器開啟一個網站就是使用HTTP協議進行資料傳輸。

HTTP協議也是基於TCP協議,所以也有伺服器和客戶端。HTTP客戶端一般是瀏覽器,當然還有可能是其他東西。HTTP伺服器,也就是Web伺服器,目前已經有很多成熟的產品,例如Apache HTTP Server、Tomcat、Nginx、IIS等。

本文的內容不是講解如何使用以上的HTTP伺服器,而是要分別用MINA、Netty、Twisted實現一個簡單的HTTP伺服器。

首先,要簡單瞭解一下HTTP協議。

HTTP協議是請求/響應式的協議,客戶端需要傳送一個請求,伺服器才會返回響應內容。例如在瀏覽器上輸入一個網址按下Enter,或者提交一個Form表單,瀏覽器就會發送一個請求到伺服器,而開啟的網頁的內容,就是伺服器返回的響應。

下面瞭解一下HTTP請求和響應包含的內容。

HTTP請求有很多種method,最常用的就是GET和POST,每種method的請求之間會有細微的區別。下面分別分析一下GET和POST請求。

GET請求:

下面是瀏覽器對http://localhost:8081/test?name=XXG&age=23的GET請求時傳送給伺服器的資料:


可以看出請求包含request line和header兩部分。其中request line中包含method(例如GET、POST)、request uri和protocol version三部分,三個部分之間以空格分開。request line和每個header各佔一行,以換行符CRLF(即\r\n)分割。

POST請求:

下面是瀏覽器對http://localhost:8081/test的POST請求時傳送給伺服器的資料,同樣帶上引數name=XXG&age=23:


可以看出,上面的請求包含三個部分:request line、header、message,比之前的GET請求多了一個message body,其中header和message body之間用一個空行分割。POST請求的引數不在URL中,而是在message body中,header中多了一項Content-Length用於表示message body的位元組數,這樣伺服器才能知道請求是否傳送結束。這也就是GET請求和POST請求的主要區別。

HTTP響應和HTTP請求非常相似,HTTP響應包含三個部分:status line、header、massage body。其中status line包含protocol version、狀態碼(status code)、reason phrase三部分。狀態碼用於描述HTTP響應的狀態,例如200表示成功,404表示資源未找到,500表示伺服器出錯。

HTTP響應:


在上面的HTTP響應中,Header中的Content-Length同樣用於表示message body的位元組數。Content-Type表示message body的型別,通常瀏覽網頁其型別是HTML,當然還會有其他型別,比如圖片、視訊等。

學習了HTTP協議後,那麼就可以分別通過MINA、Netty、Twisted實現針對請求的解碼器和針對響應的編碼器來實現一個HTTP伺服器。實際上HTTP協議的細節還有很多,自己實現起來沒那麼容易。不過,MINA、Netty、Twisted都已經提供了針對HTTP協議的編碼解碼器和一些實用的API。

下面分別用MINA、Netty、Twisted來實現一個HTTP伺服器,用瀏覽器訪問:

http://localhost:8080/?name=叉叉哥

就可以開啟一個頁面,將引數顯示在頁面上:


MINA:

MINA中有一個mina-http-2.0.7.jar包,專門用於處理HTTP協議。在下面的程式碼中,需要將這個jar包引入到專案中。

HTTP協議的請求解碼器和響應編碼器即HttpServerCodec,它會將HTTP客戶端請求轉成HttpRequest物件,將HttpResponse物件編碼成HTTP響應傳送給客戶端。需要注意的是,HttpRequest和HttpResponse的實現類物件都沒有包含message body部分,所以下面程式碼中body還通過原始的IoBuffer型別來構造。

public class HttpServer {

	public static void main(String[] args) throws IOException {
		IoAcceptor acceptor = new NioSocketAcceptor();
		acceptor.getFilterChain().addLast("codec", new HttpServerCodec());
		acceptor.setHandler(new HttpServerHandle());
		acceptor.bind(new InetSocketAddress(8080));
	}
}

class HttpServerHandle extends IoHandlerAdapter {
	
	@Override
	public void exceptionCaught(IoSession session, Throwable cause)
			throws Exception {
		cause.printStackTrace();
	}

	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {
		
		if (message instanceof HttpRequest) {
			
			// 請求,解碼器將請求轉換成HttpRequest物件
			HttpRequest request = (HttpRequest) message;
			
			// 獲取請求引數
			String name = request.getParameter("name");
			name = URLDecoder.decode(name, "UTF-8");

			// 響應HTML
			String responseHtml = "<html><body>Hello, " + name + "</body></html>";
			byte[] responseBytes = responseHtml.getBytes("UTF-8");
			int contentLength = responseBytes.length;
			
			// 構造HttpResponse物件,HttpResponse只包含響應的status line和header部分
			Map<String, String> headers = new HashMap<String, String>();
			headers.put("Content-Type", "text/html; charset=utf-8");
			headers.put("Content-Length", Integer.toString(contentLength));
			HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SUCCESS_OK, headers);
			
			// 響應BODY
			IoBuffer responseIoBuffer = IoBuffer.allocate(contentLength);
			responseIoBuffer.put(responseBytes);
	        responseIoBuffer.flip();
			
	        session.write(response); // 響應的status line和header部分
	        session.write(responseIoBuffer); // 響應body部分
		}
	}
}

Netty:

Netty和MINA非常類似。唯一有區別的地方就是FullHttpResponse可以包含響應的message body。

public class HttpServer {

	public static void main(String[] args) throws InterruptedException {
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workerGroup)
					.channel(NioServerSocketChannel.class)
					.childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						public void initChannel(SocketChannel ch) throws Exception {
							ChannelPipeline pipeline = ch.pipeline();
							pipeline.addLast(new HttpServerCodec());
							pipeline.addLast(new HttpServerHandler());
						}
					});
			ChannelFuture f = b.bind(8080).sync();
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}
}

class HttpServerHandler extends ChannelInboundHandlerAdapter {

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
		
		if (msg instanceof HttpRequest) {
			
			// 請求,解碼器將請求轉換成HttpRequest物件
			HttpRequest request = (HttpRequest) msg;
			
			// 獲取請求引數
			QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri());
			String name = queryStringDecoder.parameters().get("name").get(0);
						
			// 響應HTML
			String responseHtml = "<html><body>Hello, " + name + "</body></html>";
			byte[] responseBytes = responseHtml.getBytes("UTF-8");
			int contentLength = responseBytes.length;
			
			// 構造FullHttpResponse物件,FullHttpResponse包含message body
			FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(responseBytes));
			response.headers().set("Content-Type", "text/html; charset=utf-8");
			response.headers().set("Content-Length", Integer.toString(contentLength));

			ctx.writeAndFlush(response);
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}

Twisted:

Twisted的HTTP相比MINA、Netty來說功能最完善。Twisted不但包含HTTP協議的編碼器和解碼器以及相關API,還提供了一整套Web應用解決方案。想完整學習的話可以參考官方文件。

# -*- coding:utf-8 –*-

from twisted.web import server, resource
from twisted.internet import reactor

class MainResource(resource.Resource):
    
    isLeaf = True
    
    # 用於處理GET型別請求
    def render_GET(self, request):
        
        # name引數
        name = request.args['name'][0]
        
        # 設定響應編碼
        request.responseHeaders.addRawHeader("Content-Type", "text/html; charset=utf-8")
                
        # 響應的內容直接返回
        return "<html><body>Hello, " + name + "</body></html>"


site = server.Site(MainResource())
reactor.listenTCP(8080, site)
reactor.run()


MINA、Netty、Twisted一起學系列

原始碼