用Netty实现的简单HTTP服务器

97 篇文章 1 订阅

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

1.Server

01 public 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

01 public 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类
001 import static org.jboss.netty.handler.codec.http.HttpHeaders.is100ContinueExpected;
002  
003 import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive;
004 import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
005 import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.COOKIE;
006 import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SET_COOKIE;
007 import static org.jboss.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
008 import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;
009 import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
010  
011 import java.io.File;
012 import java.io.RandomAccessFile;
013 import java.util.List;
014 import java.util.Map;
015 import java.util.Map.Entry;
016 import java.util.Set;
017  
018 import org.jboss.netty.buffer.ChannelBuffer;
019 import org.jboss.netty.channel.Channel;
020 import org.jboss.netty.channel.ChannelFutureListener;
021 import org.jboss.netty.channel.ChannelHandlerContext;
022 import org.jboss.netty.channel.ExceptionEvent;
023 import org.jboss.netty.channel.MessageEvent;
024 import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
025 import org.jboss.netty.handler.codec.http.Cookie;
026 import org.jboss.netty.handler.codec.http.CookieDecoder;
027 import org.jboss.netty.handler.codec.http.CookieEncoder;
028 import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
029 import org.jboss.netty.handler.codec.http.HttpChunk;
030 import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
031 import org.jboss.netty.handler.codec.http.HttpHeaders;
032 import org.jboss.netty.handler.codec.http.HttpMethod;
033 import org.jboss.netty.handler.codec.http.HttpRequest;
034 import org.jboss.netty.handler.codec.http.HttpResponse;
035 import org.jboss.netty.handler.codec.http.HttpResponseStatus;
036 import org.jboss.netty.handler.codec.http.QueryStringDecoder;
037 import org.jboss.netty.handler.stream.ChunkedFile;
038 import org.jboss.netty.util.CharsetUtil;
039  
040 public 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.配置类
01 public 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.启动服务器,测试

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值