用Netty实现的简单HTTP服务器

来源:互联网 发布:网络推广员专业知识 编辑:程序博客网 时间:2024/06/06 02:46

用Netty实现的一个简单的HTTP服务器,可以处理静态文件,例子中的注释也比较全。主要是对HTTP的理解,接下来的文章中我也会更新一些HTTP相关的文章以及对例子的进一步完善,由浅到深,记录一些我的学习过程!

1.Server

01public class HttpServer {
02    public static void main(String[] args) {
03        ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
04                Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
05 
06        bootstrap.setPipelineFactory(new HttpServerPipelineFactory());
07 
08        bootstrap.bind(new InetSocketAddress(8080));
09        System.out.println("服务器已经启动,请访问http://127.0.0.1:8080/index.html进行测试!\n\n");
10    }
11}

2.Pipeline

01public class HttpServerPipelineFactory implements ChannelPipelineFactory {
02    public ChannelPipeline getPipeline() throws Exception {
03        // Create a default pipeline implementation.
04        ChannelPipeline pipeline = pipeline();
05 
06        // Uncomment the following line if you want HTTPS
07        // SSLEngine engine =
08        // SecureChatSslContextFactory.getServerContext().createSSLEngine();
09        // engine.setUseClientMode(false);
10        // pipeline.addLast("ssl", new SslHandler(engine));
11 
12        pipeline.addLast("decoder"new HttpRequestDecoder());
13        // Uncomment the following line if you don't want to handle HttpChunks.
14        // pipeline.addLast("aggregator", new HttpChunkAggregator(1048576));
15        pipeline.addLast("encoder"new HttpResponseEncoder());
16        // Remove the following line if you don't want automatic content
17        // compression.
18        //pipeline.addLast("aggregator", new HttpChunkAggregator(1048576));
19        pipeline.addLast("chunkedWriter"new ChunkedWriteHandler());
20        pipeline.addLast("deflater"new HttpContentCompressor());
21        pipeline.addLast("handler"new HttpRequestHandler());
22        return pipeline;
23    }
24}
3.handler类
001import static org.jboss.netty.handler.codec.http.HttpHeaders.is100ContinueExpected;
002 
003import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive;
004import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
005import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.COOKIE;
006import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SET_COOKIE;
007import static org.jboss.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
008import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;
009import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
010 
011import java.io.File;
012import java.io.RandomAccessFile;
013import java.util.List;
014import java.util.Map;
015import java.util.Map.Entry;
016import java.util.Set;
017 
018import org.jboss.netty.buffer.ChannelBuffer;
019import org.jboss.netty.channel.Channel;
020import org.jboss.netty.channel.ChannelFutureListener;
021import org.jboss.netty.channel.ChannelHandlerContext;
022import org.jboss.netty.channel.ExceptionEvent;
023import org.jboss.netty.channel.MessageEvent;
024import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
025import org.jboss.netty.handler.codec.http.Cookie;
026import org.jboss.netty.handler.codec.http.CookieDecoder;
027import org.jboss.netty.handler.codec.http.CookieEncoder;
028import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
029import org.jboss.netty.handler.codec.http.HttpChunk;
030import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
031import org.jboss.netty.handler.codec.http.HttpHeaders;
032import org.jboss.netty.handler.codec.http.HttpMethod;
033import org.jboss.netty.handler.codec.http.HttpRequest;
034import org.jboss.netty.handler.codec.http.HttpResponse;
035import org.jboss.netty.handler.codec.http.HttpResponseStatus;
036import org.jboss.netty.handler.codec.http.QueryStringDecoder;
037import org.jboss.netty.handler.stream.ChunkedFile;
038import org.jboss.netty.util.CharsetUtil;
039 
040public class HttpRequestHandler extends SimpleChannelUpstreamHandler {
041 
042    private HttpRequest request;
043    private boolean readingChunks;
044 
045    @Override
046    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
047        if (!readingChunks) {
048            HttpRequest request = this.request = (HttpRequest) e.getMessage();
049            String uri = request.getUri();
050            System.out.println("-----------------------------------------------------------------");
051            System.out.println("uri:"+uri);
052            System.out.println("-----------------------------------------------------------------");
053            /**
054             * 100 Continue
055             * 是这样的一种情况:HTTP客户端程序有一个实体的主体部分要发送给服务器,但希望在发送之前查看下服务器是否会
056             * 接受这个实体,所以在发送实体之前先发送了一个携带100
057             * Continue的Expect请求首部的请求。服务器在收到这样的请求后,应该用 100 Continue或一条错误码来进行响应。
058             */
059            if (is100ContinueExpected(request)) {
060                send100Continue(e);
061            }
062            // 解析http头部
063            for (Map.Entry<String, String> h : request.getHeaders()) {
064                System.out.println("HEADER: " + h.getKey() + " = " + h.getValue() + "\r\n");
065            }
066            // 解析请求参数
067            QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri());
068            Map<String, List<String>> params = queryStringDecoder.getParameters();
069            if (!params.isEmpty()) {
070                for (Entry<String, List<String>> p : params.entrySet()) {
071                    String key = p.getKey();
072                    List<String> vals = p.getValue();
073                    for (String val : vals) {
074                        System.out.println("PARAM: " + key + " = " + val + "\r\n");
075                    }
076                }
077            }
078            if (request.isChunked()) {
079                readingChunks = true;
080            else {
081                ChannelBuffer content = request.getContent();
082                if (content.readable()) {
083                    System.out.println(content.toString(CharsetUtil.UTF_8));
084                }
085                writeResponse(e, uri);
086            }
087        else {// 为分块编码时
088            HttpChunk chunk = (HttpChunk) e.getMessage();
089            if (chunk.isLast()) {
090                readingChunks = false;
091                // END OF CONTENT\r\n"
092                HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
093                if (!trailer.getHeaderNames().isEmpty()) {
094                    for (String name : trailer.getHeaderNames()) {
095                        for (String value : trailer.getHeaders(name)) {
096                            System.out.println("TRAILING HEADER: " + name + " = " + value + "\r\n");
097                        }
098                    }
099                }
100                writeResponse(e, "/");
101            else {
102                System.out.println("CHUNK: " + chunk.getContent().toString(CharsetUtil.UTF_8)
103                        "\r\n");
104            }
105        }
106    }
107 
108    private void writeResponse(MessageEvent e, String uri) {
109        // 解析Connection首部,判断是否为持久连接
110        boolean keepAlive = isKeepAlive(request);
111 
112        // Build the response object.
113        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
114        response.setStatus(HttpResponseStatus.OK);
115        // 服务端可以通过location首部将客户端导向某个资源的地址。
116        // response.addHeader("Location", uri);
117        if (keepAlive) {
118            // Add 'Content-Length' header only for a keep-alive connection.
119            response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes());
120        }
121        // 得到客户端的cookie信息,并再次写到客户端
122        String cookieString = request.getHeader(COOKIE);
123        if (cookieString != null) {
124            CookieDecoder cookieDecoder = new CookieDecoder();
125            Set<Cookie> cookies = cookieDecoder.decode(cookieString);
126            if (!cookies.isEmpty()) {
127                CookieEncoder cookieEncoder = new CookieEncoder(true);
128                for (Cookie cookie : cookies) {
129                    cookieEncoder.addCookie(cookie);
130                }
131                response.addHeader(SET_COOKIE, cookieEncoder.encode());
132            }
133        }
134        final String path = Config.getRealPath(uri);
135        File localFile = new File(path);
136        // 如果文件隐藏或者不存在
137        if (localFile.isHidden() || !localFile.exists()) {
138            // 逻辑处理
139            return;
140        }
141        // 如果请求路径为目录
142        if (localFile.isDirectory()) {
143            // 逻辑处理
144            return;
145        }
146        RandomAccessFile raf = null;
147        try {
148            raf = new RandomAccessFile(localFile, "r");
149            long fileLength = raf.length();
150            response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(fileLength));
151            Channel ch = e.getChannel();
152            ch.write(response);
153            // 这里又要重新温习下http的方法,head方法与get方法类似,但是服务器在响应中只返回首部,不会返回实体的主体部分
154            if (!request.getMethod().equals(HttpMethod.HEAD)) {
155                ch.write(new ChunkedFile(raf, 0, fileLength, 8192));//8kb
156            }
157        catch (Exception e2) {
158            e2.printStackTrace();
159        } finally {
160            if (keepAlive) {
161                response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes());
162            }
163            if (!keepAlive) {
164                e.getFuture().addListener(ChannelFutureListener.CLOSE);
165            }
166        }
167    }
168 
169    private void send100Continue(MessageEvent e) {
170        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, CONTINUE);
171        e.getChannel().write(response);
172    }
173 
174    @Override
175    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
176        e.getCause().printStackTrace();
177        e.getChannel().close();
178    }
179}
4.配置类
01public class Config {
02 
03    public static String getRealPath(String uri) {
04        StringBuilder sb=new StringBuilder("/home/guolei/workspace/Test/web");
05        sb.append(uri);
06        if (!uri.endsWith("/")) {
07            sb.append('/');
08        }
09        return sb.toString();
10    }
11}

5.页面

在项目中新建一个文件夹,名称为web(可以在配置中配置),在文件夹中放入静态页面index.html。

6.启动服务器,测试

0 1
原创粉丝点击