先转载一篇 【初学与研发之NETTY】netty3之文件上传 http://blog.csdn.net/mcpang/article/details/41139859

  1. 客户端:
  2. [java] view plain copy
  3.  
  4. package netty3.socket.client;
  5.  
  6. import static org.jboss.netty.channel.Channels.pipeline;
  7.  
  8. import java.io.File;
  9. import java.net.InetSocketAddress;
  10. import java.util.List;
  11. import java.util.concurrent.Executors;
  12.  
  13. import org.jboss.netty.bootstrap.ClientBootstrap;
  14. import org.jboss.netty.buffer.ChannelBuffer;
  15. import org.jboss.netty.channel.Channel;
  16. import org.jboss.netty.channel.ChannelFuture;
  17. import org.jboss.netty.channel.ChannelHandlerContext;
  18. import org.jboss.netty.channel.ChannelPipeline;
  19. import org.jboss.netty.channel.ChannelPipelineFactory;
  20. import org.jboss.netty.channel.ExceptionEvent;
  21. import org.jboss.netty.channel.MessageEvent;
  22. import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
  23. import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
  24. import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
  25. import org.jboss.netty.handler.codec.http.HttpChunk;
  26. import org.jboss.netty.handler.codec.http.HttpClientCodec;
  27. import org.jboss.netty.handler.codec.http.HttpHeaders;
  28. import org.jboss.netty.handler.codec.http.HttpMethod;
  29. import org.jboss.netty.handler.codec.http.HttpRequest;
  30. import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
  31. import org.jboss.netty.handler.codec.http.HttpResponse;
  32. import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
  33. import org.jboss.netty.handler.codec.http.HttpVersion;
  34. import org.jboss.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
  35. import org.jboss.netty.handler.codec.http.multipart.HttpDataFactory;
  36. import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestEncoder;
  37. import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData;
  38. import org.jboss.netty.handler.stream.ChunkedWriteHandler;
  39. import org.jboss.netty.util.CharsetUtil;
  40.  
  41. public class UploadFileClient
  42. {
  43. private ClientBootstrap bootstrap = null;
  44.  
  45. private ChannelFuture future = null;
  46.  
  47. private HttpDataFactory factory = null;
  48.  
  49. // 服务端处理完成后返回的消息
  50. private StringBuffer retMsg = new StringBuffer();
  51.  
  52. public UploadFileClient()
  53. {
  54. bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
  55. bootstrap.setPipelineFactory(new UploadChannelFactory());
  56.  
  57. // 连接超时时间为3s
  58. bootstrap.setOption("connectTimeoutMillis", 3000);
  59.  
  60. future = bootstrap.connect(new InetSocketAddress("127.0.0.1", 2777));
  61.  
  62. // 获得一个阈值,它是来控制上传文件时内存/硬盘的比值,防止出现内存溢出
  63. factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
  64. }
  65.  
  66. /**
  67. * 方法描述:关闭文件发送通道(为阻塞式)
  68. */
  69. public void shutdownClient()
  70. {
  71. // 等待数据的传输通道关闭
  72. future.getChannel().getCloseFuture().awaitUninterruptibly();
  73.  
  74. bootstrap.releaseExternalResources();
  75.  
  76. // Really clean all temporary files if they still exist
  77. factory.cleanAllHttpDatas();
  78. }
  79.  
  80. /**
  81. * 方法描述:获取发送文件过程中服务端反馈的消息
  82. * @return 服务端反馈的消息
  83. */
  84. public String getRetMsg()
  85. {
  86. return retMsg.toString();
  87. }
  88.  
  89. /**
  90. * 方法描述:将文件上传到服务端
  91. * @param file 待上传的文件
  92. */
  93. public void uploadFile(File file)
  94. {
  95. if (!file.canRead())
  96. {
  97. return;
  98. }
  99.  
  100. // Simple Post form: factory used for big attributes
  101. List<InterfaceHttpData> bodylist = formpost(file);
  102. if (bodylist == null)
  103. {
  104. return;
  105. }
  106.  
  107. // Multipart Post form: factory used
  108. uploadFileToServer(file.getName(), factory, bodylist);
  109. }
  110.  
  111. /**
  112. * @param file
  113. * @return
  114. */
  115. private List<InterfaceHttpData> formpost(File file)
  116. {
  117. // Prepare the HTTP request.
  118. HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "");
  119.  
  120. // Use the PostBody encoder
  121. HttpPostRequestEncoder bodyRequestEncoder = null;
  122. try
  123. {
  124. bodyRequestEncoder = new HttpPostRequestEncoder(factory, request, false);
  125. bodyRequestEncoder.addBodyAttribute("getform", "POST");
  126. bodyRequestEncoder.addBodyFileUpload("myfile", file, "application/x-zip-compressed", false);
  127. }
  128. catch(Exception e)
  129. {
  130. // should not be since args are not null
  131. e.printStackTrace();
  132. return null;
  133. }
  134.  
  135. // Create the bodylist to be reused on the last version with Multipart support
  136. List<InterfaceHttpData> bodylist = bodyRequestEncoder.getBodyListAttributes();
  137.  
  138. return bodylist;
  139. }
  140.  
  141. /**
  142. * Multipart example
  143. */
  144. private void uploadFileToServer(String fileName, HttpDataFactory factory, List<InterfaceHttpData> bodylist)
  145. {
  146. // Wait until the connection attempt succeeds or fails.
  147. Channel channel = future.awaitUninterruptibly().getChannel();
  148. if (!future.isSuccess())
  149. {
  150. future.getCause().printStackTrace();
  151. bootstrap.releaseExternalResources();
  152. return;
  153. }
  154.  
  155. // Prepare the HTTP request.
  156. HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, fileName);
  157.  
  158. // 设置该属性表示服务端文件接收完毕后会关闭发送通道
  159. request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);
  160.  
  161. // Use the PostBody encoder
  162. HttpPostRequestEncoder bodyRequestEncoder = null;
  163. try
  164. {
  165. bodyRequestEncoder = new HttpPostRequestEncoder(factory, request, true);
  166. bodyRequestEncoder.setBodyHttpDatas(bodylist);
  167. bodyRequestEncoder.finalizeRequest();
  168. }
  169. catch(Exception e)
  170. {
  171. // should not be since no null args
  172. e.printStackTrace();
  173. }
  174. System.out.println("开始时间:"+System.currentTimeMillis());
  175. // send request
  176. channel.write(request);
  177.  
  178. // test if request was chunked and if so, finish the write
  179. if (bodyRequestEncoder.isChunked())
  180. {
  181. channel.write(bodyRequestEncoder).awaitUninterruptibly();
  182. }
  183.  
  184. // Now no more use of file representation (and list of HttpData)
  185. bodyRequestEncoder.cleanFiles();
  186. }
  187.  
  188. private class UploadChannelFactory implements ChannelPipelineFactory
  189. {
  190.  
  191. public ChannelPipeline getPipeline() throws Exception
  192. {
  193. ChannelPipeline pipeline = pipeline();
  194.  
  195. pipeline.addLast("decoder", new HttpResponseDecoder());
  196. pipeline.addLast("encoder", new HttpRequestEncoder());
  197. pipeline.addLast("codec", new HttpClientCodec());
  198. pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
  199. pipeline.addLast("handler", new UploadClientHandler());
  200.  
  201. return pipeline;
  202. }
  203. }
  204.  
  205. private class UploadClientHandler extends SimpleChannelUpstreamHandler
  206. {
  207. private boolean readingChunks;
  208.  
  209. /**
  210. * 方法描述:接收服务端返回的消息
  211. * @param ctx 发送消息的通道对象
  212. * @param e 消息发送事件对象
  213. */
  214. public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception
  215. {
  216. if (!readingChunks)
  217. {
  218. HttpResponse response = (HttpResponse)e.getMessage();
  219.  
  220. // 收到服务端反馈的消息,并且链接正常、且还有后续消息
  221. if (response.getStatus().getCode() == 200 && response.isChunked())
  222. {
  223. readingChunks = true;
  224. }
  225. else
  226. {
  227. // 服务端有反馈消息,但没有后续的消息了
  228. ChannelBuffer content = response.getContent();
  229. if (content.readable())
  230. {
  231. retMsg.append(content.toString(CharsetUtil.UTF_8));
  232. }
  233. }
  234. }
  235. else
  236. {
  237. HttpChunk chunk = (HttpChunk)e.getMessage();
  238. if (chunk.isLast())
  239. {
  240. // 服务端的消息接收完毕
  241. readingChunks = false;
  242. }
  243. else
  244. {
  245. // 连续接收服务端发过来的消息
  246. retMsg.append(chunk.getContent().toString(CharsetUtil.UTF_8));
  247. }
  248. }
  249. }
  250.  
  251. /**
  252. * 方法描述:消息接收或发送过程中出现异常
  253. * @param ctx 发送消息的通道对象
  254. * @param e 异常事件对象
  255. */
  256. public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception
  257. {
  258. System.out.println("异常--:" + e.getCause());
  259. e.getChannel().close();
  260.  
  261. // 有异常后释放客户端占用的通道资源
  262. shutdownClient();
  263. }
  264. }
  265. }
  266.  
  267. 服务端:
  268. [java] view plain copy
  269.  
  270. package netty3.socket.server;
  271.  
  272. import static org.jboss.netty.channel.Channels.pipeline;
  273.  
  274. import java.net.InetSocketAddress;
  275. import java.util.concurrent.Executors;
  276.  
  277. import org.jboss.netty.bootstrap.ServerBootstrap;
  278. import org.jboss.netty.channel.ChannelPipeline;
  279. import org.jboss.netty.channel.ChannelPipelineFactory;
  280. import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
  281. import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
  282. import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
  283. import org.jboss.netty.handler.stream.ChunkedWriteHandler;
  284.  
  285. public class InitServer
  286. {
  287. private static InitServer sockServer = null;
  288.  
  289. private static ServerBootstrap bootstrap = null;
  290.  
  291. public static InitServer getInstance()
  292. {
  293. if (sockServer == null)
  294. {
  295. sockServer = new InitServer();
  296. }
  297. return sockServer;
  298. }
  299.  
  300. public InitServer()
  301. {
  302. bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
  303.  
  304. bootstrap.setPipelineFactory(new ChannelPipelineFactory()
  305. {
  306. public ChannelPipeline getPipeline() throws Exception
  307. {
  308. ChannelPipeline pipeline = pipeline();
  309. pipeline.addLast("decoder", new HttpRequestDecoder());
  310. pipeline.addLast("encoder", new HttpResponseEncoder());
  311. pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
  312. pipeline.addLast("handler", new ServerHandler());
  313.  
  314. return pipeline;
  315. }
  316.  
  317. });
  318.  
  319. bootstrap.bind(new InetSocketAddress("127.0.0.1", 2777));
  320. }
  321.  
  322. public void shutdownServer()
  323. {
  324. bootstrap.releaseExternalResources();
  325. }
  326. }
  327.  
  328. [java] view plain copy
  329.  
  330. package netty3.socket.server;
  331.  
  332. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CACHE_CONTROL;
  333. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
  334. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.DATE;
  335. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.EXPIRES;
  336. import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.LAST_MODIFIED;
  337. import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
  338.  
  339. import java.io.File;
  340. import java.io.FileNotFoundException;
  341. import java.io.IOException;
  342. import java.io.RandomAccessFile;
  343. import java.io.UnsupportedEncodingException;
  344. import java.net.URLDecoder;
  345. import java.text.SimpleDateFormat;
  346. import java.util.Calendar;
  347. import java.util.Date;
  348. import java.util.GregorianCalendar;
  349. import java.util.HashMap;
  350. import java.util.List;
  351. import java.util.Locale;
  352. import java.util.Map;
  353. import java.util.Random;
  354. import java.util.TimeZone;
  355.  
  356. import javax.activation.MimetypesFileTypeMap;
  357.  
  358. import netty3.socket.client.SendMsgClient;
  359.  
  360. import org.jboss.netty.buffer.ChannelBuffer;
  361. import org.jboss.netty.buffer.ChannelBuffers;
  362. import org.jboss.netty.channel.Channel;
  363. import org.jboss.netty.channel.ChannelFuture;
  364. import org.jboss.netty.channel.ChannelFutureListener;
  365. import org.jboss.netty.channel.ChannelFutureProgressListener;
  366. import org.jboss.netty.channel.ChannelHandlerContext;
  367. import org.jboss.netty.channel.ChannelStateEvent;
  368. import org.jboss.netty.channel.Channels;
  369. import org.jboss.netty.channel.DefaultFileRegion;
  370. import org.jboss.netty.channel.ExceptionEvent;
  371. import org.jboss.netty.channel.FileRegion;
  372. import org.jboss.netty.channel.MessageEvent;
  373. import org.jboss.netty.channel.SimpleChannelHandler;
  374. import org.jboss.netty.handler.codec.frame.TooLongFrameException;
  375. import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
  376. import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
  377. import org.jboss.netty.handler.codec.http.HttpChunk;
  378. import org.jboss.netty.handler.codec.http.HttpHeaders;
  379. import org.jboss.netty.handler.codec.http.HttpMethod;
  380. import org.jboss.netty.handler.codec.http.HttpRequest;
  381. import org.jboss.netty.handler.codec.http.HttpResponse;
  382. import org.jboss.netty.handler.codec.http.HttpResponseStatus;
  383. import org.jboss.netty.handler.codec.http.HttpVersion;
  384. import org.jboss.netty.handler.codec.http.multipart.Attribute;
  385. import org.jboss.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
  386. import org.jboss.netty.handler.codec.http.multipart.DiskFileUpload;
  387. import org.jboss.netty.handler.codec.http.multipart.FileUpload;
  388. import org.jboss.netty.handler.codec.http.multipart.HttpDataFactory;
  389. import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
  390. import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
  391. import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData;
  392. import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
  393. import org.jboss.netty.handler.ssl.SslHandler;
  394. import org.jboss.netty.handler.stream.ChunkedFile;
  395. import org.jboss.netty.util.CharsetUtil;
  396.  
  397. public class ServerHandler extends SimpleChannelHandler
  398. {
  399. public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
  400.  
  401. public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";
  402.  
  403. public static final int HTTP_CACHE_SECONDS = 60;
  404.  
  405. private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk if size exceed MINSIZE
  406.  
  407. private HttpPostRequestDecoder decoder;
  408.  
  409. private HttpRequest request;
  410.  
  411. private String receiveFileName = "";
  412.  
  413. private Map<String, String> msgMap = new HashMap<String, String>();
  414.  
  415. private boolean readingChunks = false;
  416.  
  417. static
  418. {
  419. DiskFileUpload.baseDirectory = "/home/build1/file_test/";
  420. }
  421.  
  422. public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception
  423. {
  424. if (e.getMessage() instanceof HttpRequest)
  425. {
  426. HttpRequest request = (DefaultHttpRequest)e.getMessage();
  427. String uri = sanitizeUri(request.getUri());
  428.  
  429. System.out.println(request.isChunked());
  430.  
  431. if (request.getMethod() == HttpMethod.POST)
  432. {
  433. // 接收客户端上传的文件
  434. receiveFileName = uri;
  435. this.request = request;
  436.  
  437. // clean previous FileUpload if Any
  438. if (decoder != null)
  439. {
  440. decoder.cleanFiles();
  441. decoder = null;
  442. }
  443.  
  444. // if GET Method: should not try to create a HttpPostRequestDecoder
  445. try
  446. {
  447. decoder = new HttpPostRequestDecoder(factory, request);
  448. }
  449. catch(Exception e1)
  450. {
  451. e1.printStackTrace();
  452. writeResponse(e.getChannel(), "接收文件信息时出现异常:" + e1.toString());
  453. Channels.close(e.getChannel());
  454. return;
  455. }
  456.  
  457. if (!request.isChunked())
  458. {
  459. readHttpDataAllReceive(e.getChannel());
  460. writeResponse(e.getChannel(), "服务端文件接收完毕!");
  461. }
  462. }
  463. }
  464. else
  465. {
  466. // New chunk is received
  467. HttpChunk chunk = (HttpChunk)e.getMessage();
  468. // example of reading only if at the end
  469. if (!chunk.isLast())
  470. {
  471. try
  472. {
  473. decoder.offer(chunk);
  474. }
  475. catch(Exception e1)
  476. {
  477. e1.printStackTrace();
  478. writeResponse(e.getChannel(), "接收文件数据时出现异常:" + e1.toString());
  479. Channels.close(e.getChannel());
  480. return;
  481. }
  482.  
  483. // example of reading chunk by chunk (minimize memory usage due to Factory)
  484. readHttpDataChunkByChunk();
  485.  
  486. } else {
  487. readHttpDataAllReceive(e.getChannel()); //最后数据
  488. //writeResponse(e.getChannel(), "服务端数据接收完毕!");
  489. String sendMsg = msgMap.get("sendMsg");
  490. System.out.println("服务端收到消息:" + sendMsg);
  491.  
  492. sendReturnMsg(ctx, HttpResponseStatus.OK, "服务端返回的消息!");
  493. }
  494. }
  495. }
  496.  
  497. /**
  498. * Example of reading all InterfaceHttpData from finished transfer
  499. */
  500. private void readHttpDataAllReceive(Channel channel)
  501. {
  502. List<InterfaceHttpData> datas;
  503. try
  504. {
  505. datas = decoder.getBodyHttpDatas();
  506. }
  507. catch(Exception e1)
  508. {
  509. e1.printStackTrace();
  510. writeResponse(channel, "接收文件数据时出现异常:" + e1.toString());
  511. Channels.close(channel);
  512. return;
  513. }
  514.  
  515. for (InterfaceHttpData data : datas)
  516. {
  517. writeHttpData(data);
  518. }
  519. }
  520.  
  521. /**
  522. * Example of reading request by chunk and getting values from chunk to chunk
  523. */
  524. private void readHttpDataChunkByChunk()
  525. {
  526. try
  527. {
  528. while(decoder.hasNext())
  529. {
  530. InterfaceHttpData data = decoder.next();
  531. if (data != null)
  532. {
  533. // new value
  534. writeHttpData(data);
  535. }
  536. }
  537. }
  538. catch(EndOfDataDecoderException e1)
  539. {
  540. e1.printStackTrace();
  541. }
  542. }
  543.  
  544. private void writeHttpData(InterfaceHttpData data)
  545. {
  546. if (data.getHttpDataType() == HttpDataType.FileUpload)
  547. {
  548. FileUpload fileUpload = (FileUpload)data;
  549. if (fileUpload.isCompleted())
  550. {
  551. try
  552. {
  553. Random r = new Random();
  554. StringBuffer fileNameBuf = new StringBuffer();
  555. fileNameBuf.append(DiskFileUpload.baseDirectory).append("U").append(System.currentTimeMillis());
  556. fileNameBuf.append(String.valueOf(r.nextInt(10))).append(String.valueOf(r.nextInt(10)));
  557. fileNameBuf.append(receiveFileName.substring(receiveFileName.lastIndexOf(".")));
  558.  
  559. fileUpload.renameTo(new File(fileNameBuf.toString()));
  560. }
  561. catch(IOException e)
  562. {
  563. e.printStackTrace();
  564. }
  565. System.out.println("结束时间:"+System.currentTimeMillis());
  566. }
  567. else
  568. {
  569. System.out.println("\tFile to be continued but should not!\r\n");
  570. }
  571. }
  572. else if (data.getHttpDataType() == HttpDataType.Attribute)
  573. {
  574. Attribute attribute = (Attribute)data;
  575. try
  576. {
  577. msgMap.put(attribute.getName(), attribute.getString());
  578. }
  579. catch(IOException e)
  580. {
  581. e.printStackTrace();
  582. }
  583. }
  584. }
  585.  
  586. private void writeResponse(Channel channel, String retMsg)
  587. {
  588. // Convert the response content to a ChannelBuffer.
  589. ChannelBuffer buf = ChannelBuffers.copiedBuffer(retMsg, CharsetUtil.UTF_8);
  590.  
  591. // Decide whether to close the connection or not.
  592. boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION))
  593. || request.getProtocolVersion().equals(HttpVersion.HTTP_1_0)
  594. && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION));
  595.  
  596. // Build the response object.
  597. HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
  598. response.setContent(buf);
  599. response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8");
  600.  
  601. if (!close)
  602. {
  603. // There's no need to add 'Content-Length' header
  604. // if this is the last response.
  605. response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(buf.readableBytes()));
  606. }
  607.  
  608. // Write the response.
  609. ChannelFuture future = channel.write(response);
  610. // Close the connection after the write operation is done if necessary.
  611. if (close)
  612. {
  613. future.addListener(ChannelFutureListener.CLOSE);
  614. }
  615. }
  616.  
  617. private String sanitizeUri(String uri)
  618. {
  619. try
  620. {
  621. uri = URLDecoder.decode(uri, "UTF-8");
  622. }
  623. catch(UnsupportedEncodingException e)
  624. {
  625. try
  626. {
  627. uri = URLDecoder.decode(uri, "ISO-8859-1");
  628. }
  629. catch(UnsupportedEncodingException e1)
  630. {
  631. throw new Error();
  632. }
  633. }
  634.  
  635. return uri;
  636. }
  637.  
  638. /**
  639. * 方法描述:设置请求响应的header信息
  640. * @param response 请求响应对象
  641. * @param fileToCache 下载文件
  642. */
  643. private static void setContentTypeHeader(HttpResponse response, File fileToCache)
  644. {
  645. MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
  646. response.setHeader(CONTENT_TYPE, mimeTypesMap.getContentType(fileToCache.getPath()));
  647.  
  648. SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
  649. dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));
  650.  
  651. // Date header
  652. Calendar time = new GregorianCalendar();
  653. response.setHeader(DATE, dateFormatter.format(time.getTime()));
  654.  
  655. // Add cache headers
  656. time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
  657. response.setHeader(EXPIRES, dateFormatter.format(time.getTime()));
  658. response.setHeader(CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
  659. response.setHeader(LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
  660. }
  661.  
  662. /**
  663. * 方法描述:给客户端发送反馈消息
  664. * @param ctx 发送消息的通道
  665. * @param status 状态
  666. * @param retMsg 反馈消息
  667. */
  668. private static void sendReturnMsg(ChannelHandlerContext ctx, HttpResponseStatus status, String retMsg)
  669. {
  670. HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);
  671. response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8");
  672. response.setContent(ChannelBuffers.copiedBuffer(retMsg, CharsetUtil.UTF_8));
  673.  
  674. // 信息发送成功后,关闭连接通道
  675. ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);
  676. }
  677.  
  678. public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception
  679. {
  680. if (decoder != null)
  681. {
  682. decoder.cleanFiles();
  683. }
  684. System.out.println("连接断开:" + e.getChannel().getRemoteAddress().toString());
  685. }
  686.  
  687. public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception
  688. {
  689. String remoteIp = e.getChannel().getRemoteAddress().toString();
  690. System.out.println(remoteIp.substring(1, remoteIp.indexOf(":")));
  691. System.out.println("收到连接:" + e.getChannel().getRemoteAddress().toString());
  692. }
  693.  
  694. @Override
  695. public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception
  696. {
  697. Channel ch = e.getChannel();
  698. Throwable cause = e.getCause();
  699. if (cause instanceof TooLongFrameException)
  700. {
  701. return;
  702. }
  703.  
  704. System.err.println("连接的通道出现异常:" + cause.toString());
  705. if (ch.isConnected())
  706. {
  707. System.out.println("连接还没有关闭!");
  708. ch.close();
  709. }
  710. }
  711.  
  712. }

我在此基础上仿写了一个http server, 但是当执行到 readHttpDataAllReceive(e.getChannel()); //最后数据  这里面的方法体datas = decoder.getBodyHttpDatas();的时候就会报错,我首先忽略这部分异常不返回,还是继续走下面的读取代码,虽然程序没有报错,但是最后获取到的数据就丢失了部分

回头看看datas = decoder.getBodyHttpDatas(); 这里报的异常是org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder$NotEnoughDataDecoderException
我们看看netty 3.07的源码
  1. public List<InterfaceHttpData> getBodyHttpDatas()
  2. throws NotEnoughDataDecoderException {
  3. if (!isLastChunk) {
  4. throw new NotEnoughDataDecoderException();
  5. }
  6. return bodyListHttpData;
  7. }

isLastChunk=false得时候就会触发这个异常,但是我们之前的代码流程不是已经到了最后一个chunk了吗,怎么回事?我们再看看isLastChunk的代码

  1. public void offer(HttpChunk chunk) throws ErrorDataDecoderException {
  2. ChannelBuffer chunked = chunk.getContent();
  3. if (undecodedChunk == null) {
  4. undecodedChunk = chunked;
  5. } else {
  6. //undecodedChunk = ChannelBuffers.wrappedBuffer(undecodedChunk, chunk.getContent());
  7. // less memory usage
  8. undecodedChunk = ChannelBuffers.wrappedBuffer(
  9. undecodedChunk, chunked);
  10. }
  11. if (chunk.isLast()) {
  12. isLastChunk = true;
  13. }
  14. parseBody();
  15. }

offer方法里会触发isLastChunk=true, 那问题就清晰了,我们再回到readHttpDataAllReceive(e.getChannel()); //最后数据 这段代码里,在这之前也加上

  1.  
  1. try
  2. {
  3. decoder.offer(chunk);
  4. }
  5. catch(Exception e1)
  6. {
  7. e1.printStackTrace();
  8. return;
  9. }
  1.  

问题解决,接收数据也完整

stackoverflow也提及过这个问题

https://stackoverflow.com/questions/23989217/posting-data-to-netty-with-apache-httpclient

To solve the problem you either need to offer() all chunks (HttpContent) of a message to HttpPostRequestDecoder before calling getBodyHttpDatas(), or alternatively you can just add the HttpObjectAggregator handler right before your handler to the channel's pipeline. If you do so, HttpObjectAggregator will collect all chunks for you and produce a single FullHttpRequest in place of multiple chunks. Passing FullHttpRequest instead of an ordinary HttpRequest to HttpPostRequestDecoder's constructor eliminates need to offer() chunks.

意思是通过offer方法把一个http请求的若干个chunk合在一起,在调用getBodyHttpDatas()前必须使用offer()方法把message完整化

刚开始写这个http server的时候,客户端发送数据很少,包体不会拆分成若干个chunk,一切都很正常,后来当客户端发送数据很大的时候,使用之前的httpRequest根本没有办法获取到数据

PS: 一个http请求里的拆分成若干个chunk后,channelid是一样的, 我们通过这个channelid来重新组装数据

netty 3.x 实现http server和遇到的坑的更多相关文章

  1. dubbo源码分析4-基于netty的dubbo协议的server

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  2. Netty源码—一、server启动(1)

    Netty作为一个Java生态中的网络组件有着举足轻重的位置,各种开源中间件都使用Netty进行网络通信,比如Dubbo.RocketMQ.可以说Netty是对Java NIO的封装,比如ByteBu ...

  3. SQL SERVER 2012/2014 链接到 SQL SERVER 2000的各种坑

    本文总结一下SQL SERVER 2012/2014链接到SQL SERVER 2000的各种坑,都是在实际应用中遇到的疑难杂症.可能会有人说怎么还在用SQL SERVER 2000,为什么不升级呢? ...

  4. ubuntu下搭建node server的几个坑

    [ubuntu下搭建node server的几个坑] 1.环境变量 process.env.PORT需要使用 export PORT=80设置 windows下是set PORT=80 2.命令连结 ...

  5. Windows Server 2012搭建SQL Server Always On踩坑全记录

    Windows Server 2012搭建SQL Server Always On踩坑全记录 环境信息: Windows Server 2012 R2 Sql Server 2012 整个搭建集群的过 ...

  6. Netty源码—二、server启动(2)

    我们在使用Netty的时候的初始化代码一般如下 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGro ...

  7. 基于netty的一款http server

    cicada 基本功能 1.基于注解.注解扫描 2.ioc 对象管理 3.intercepter 拦截器 ref https://github.com/TogetherOS/cicada

  8. JMeter一个错误the target server failed to respond--JMeter坑

    问题:1.在测试一个http景象,特别是集波动TPS时刻,出现了一个错误.它现在是一个必须错误(压力顺利时却零星的错误,甚至很少见): 每次必现错误(開始一直怀疑是网络或程序的问题)   2.失败事务 ...

  9. Microsoft SQL Server on Linux 踩坑指南

    微软用 SQL Server 在 2016 年的时候搞了一个大新闻,宣传 Microsoft ❤️ Linux 打得一众软粉措手不及.但是这还是好事情,Linux 上也有好用的 SQL Server ...

随机推荐

  1. python------模块定义、导入、优化 ------->hashlib模块

    一.hashlib模块 用于加密相关的操作,3.x版本里代替了md5模块和sha模块,主要提供SHA1,SHA224,SHA256,SHA384,SHA512,MD5算法. (MD5消息摘要算法(英语 ...

  2. CF使用TGP下载后,分卷文件损坏的解决方法

    首先从游戏的列表删除游戏(安装失败出现分卷文件损坏的游戏) 然后进入游戏重新,继续找到该游戏(安装失败的游戏) 点击下载游戏!不会重新下载的,之后下载一些失败的文件,不会花费多少时间,慢慢等待即可 之 ...

  3. django+uwsgi+nginx数据表过大引起"out of memory for query result"

    昨天负责的一个项目突然爆“out of memory for query result”. 背景 项目的数据表是保存超过10m的文本数据,通过json方式保存进postgres中,上传一个13m的大文 ...

  4. phpdocumentor安装和使用总结

    为了解决一校友在安装和使用phpDocumentor过程中遇到的问题,自己闲时也折腾了一下这个东西,总结见下: 一.定义: 自己刚听到这个词时还不知道这个是什么东西,干啥用的,就去百度了一下,说道: ...

  5. 数据格式转换(一)PDF转换技术

         PDF(Portable Document Format)文件格式是Adobe公司开发的电子文件格式. 这样的文件格式与操作系统平台无关.这一特点使它成为在Internet上进行电子文档发行 ...

  6. NPOI之Excel——设置单元格背景色

    NPOI Excel 单元格颜色对照表,在引用了 NPOI.dll 后可通过 ICellStyle 接口的 FillForegroundColor 属性实现 Excel 单元格的背景色设置,FillP ...

  7. git 查看提交历史

    查看提交历史 git log 查看每次提交的具体改动内容 git log -p 查看某个文件历次提交的具体改动内容 git log -p <file name> # git log -p ...

  8. [转]TA-Lib 安装

    转自:https://mrjbq7.github.io/ta-lib/install.html Installation You can install from PyPI: $ pip instal ...

  9. Kafka使用log.retention.hours改变消息端的消息保存时间

    数据存储的最大时间超过这个时间会根据log.cleanup.policy设置的策略处理数据,也就是消费端能够多久去消费数据log.retention.bytes和log.retention.hours ...

  10. Oracle和SQL语句的优化策略(基础篇)

    转载自: http://blog.csdn.net/houpengfei111/article/details/9245337 http://blog.csdn.net/uniqed/article/ ...