001: /*
002: * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/httpcore/tags/4.0-beta1/module-nio/src/examples/org/apache/http/examples/nio/NHttpFileServer.java $
003: * $Revision: 610763 $
004: * $Date: 2008-01-10 13:01:13 +0100 (Thu, 10 Jan 2008) $
005: *
006: * ====================================================================
007: * Licensed to the Apache Software Foundation (ASF) under one
008: * or more contributor license agreements. See the NOTICE file
009: * distributed with this work for additional information
010: * regarding copyright ownership. The ASF licenses this file
011: * to you under the Apache License, Version 2.0 (the
012: * "License"); you may not use this file except in compliance
013: * with the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing,
018: * software distributed under the License is distributed on an
019: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020: * KIND, either express or implied. See the License for the
021: * specific language governing permissions and limitations
022: * under the License.
023: * ====================================================================
024: *
025: * This software consists of voluntary contributions made by many
026: * individuals on behalf of the Apache Software Foundation. For more
027: * information on the Apache Software Foundation, please see
028: * <http://www.apache.org/>.
029: *
030: */
031: package org.apache.http.examples.nio;
032:
033: import java.io.File;
034: import java.io.IOException;
035: import java.io.InterruptedIOException;
036: import java.io.RandomAccessFile;
037: import java.io.UnsupportedEncodingException;
038: import java.net.InetSocketAddress;
039: import java.net.URLDecoder;
040: import java.nio.ByteBuffer;
041: import java.nio.channels.Channels;
042: import java.nio.channels.FileChannel;
043: import java.nio.channels.ReadableByteChannel;
044: import java.nio.channels.WritableByteChannel;
045:
046: import org.apache.http.ConnectionReuseStrategy;
047: import org.apache.http.Header;
048: import org.apache.http.HttpConnection;
049: import org.apache.http.HttpEntity;
050: import org.apache.http.HttpEntityEnclosingRequest;
051: import org.apache.http.HttpException;
052: import org.apache.http.HttpRequest;
053: import org.apache.http.HttpResponse;
054: import org.apache.http.HttpResponseFactory;
055: import org.apache.http.HttpStatus;
056: import org.apache.http.HttpVersion;
057: import org.apache.http.ProtocolVersion;
058: import org.apache.http.impl.DefaultConnectionReuseStrategy;
059: import org.apache.http.impl.DefaultHttpResponseFactory;
060: import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
061: import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
062: import org.apache.http.nio.ContentDecoder;
063: import org.apache.http.nio.ContentEncoder;
064: import org.apache.http.nio.FileContentDecoder;
065: import org.apache.http.nio.FileContentEncoder;
066: import org.apache.http.nio.NHttpServerConnection;
067: import org.apache.http.nio.NHttpServiceHandler;
068: import org.apache.http.nio.entity.ByteArrayNIOEntity;
069: import org.apache.http.nio.entity.FileNIOEntity;
070: import org.apache.http.nio.entity.HttpNIOEntity;
071: import org.apache.http.nio.reactor.IOEventDispatch;
072: import org.apache.http.nio.reactor.ListeningIOReactor;
073: import org.apache.http.params.BasicHttpParams;
074: import org.apache.http.params.HttpConnectionParams;
075: import org.apache.http.params.HttpParams;
076: import org.apache.http.params.HttpProtocolParams;
077: import org.apache.http.params.DefaultedHttpParams;
078: import org.apache.http.protocol.BasicHttpProcessor;
079: import org.apache.http.protocol.ExecutionContext;
080: import org.apache.http.protocol.HTTP;
081: import org.apache.http.protocol.HttpContext;
082: import org.apache.http.protocol.HttpProcessor;
083: import org.apache.http.protocol.ResponseConnControl;
084: import org.apache.http.protocol.ResponseContent;
085: import org.apache.http.protocol.ResponseDate;
086: import org.apache.http.protocol.ResponseServer;
087: import org.apache.http.util.EncodingUtils;
088:
089: public class NHttpFileServer {
090:
091: public static void main(String[] args) throws Exception {
092: if (args.length < 1) {
093: System.err
094: .println("Please specify document root directory");
095: System.exit(1);
096: }
097: boolean useFileChannels = true;
098: if (args.length >= 2) {
099: String s = args[1];
100: if (s.equalsIgnoreCase("disableFileChannels")) {
101: useFileChannels = false;
102: }
103: }
104: HttpParams params = new BasicHttpParams();
105: params.setIntParameter(HttpConnectionParams.SO_TIMEOUT, 20000)
106: .setIntParameter(
107: HttpConnectionParams.SOCKET_BUFFER_SIZE,
108: 8 * 1024).setBooleanParameter(
109: HttpConnectionParams.STALE_CONNECTION_CHECK,
110: false).setBooleanParameter(
111: HttpConnectionParams.TCP_NODELAY, true)
112: .setParameter(HttpProtocolParams.ORIGIN_SERVER,
113: "HttpComponents/1.1");
114:
115: ListeningIOReactor ioReactor = new DefaultListeningIOReactor(2,
116: params);
117:
118: NHttpServiceHandler handler = new FileServiceHandler(args[0],
119: useFileChannels, params);
120: IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(
121: handler, params);
122:
123: try {
124: ioReactor.listen(new InetSocketAddress(8080));
125: ioReactor.execute(ioEventDispatch);
126: } catch (InterruptedIOException ex) {
127: System.err.println("Interrupted");
128: } catch (IOException e) {
129: System.err.println("I/O error: " + e.getMessage());
130: }
131: System.out.println("Shutdown");
132: }
133:
134: private static final String CONN_STATE = "http.conn-state";
135:
136: static class ConnState {
137:
138: private final ByteBuffer inbuf;
139: private final ByteBuffer outbuf;
140:
141: private File fileHandle;
142: private RandomAccessFile inputFile;
143: private WritableByteChannel inputChannel;
144: private ReadableByteChannel outputChannel;
145: private long inputCount;
146: private long outputCount;
147:
148: public ConnState() {
149: super ();
150: this .inbuf = ByteBuffer.allocateDirect(2048);
151: this .outbuf = ByteBuffer.allocateDirect(2048);
152: }
153:
154: public ByteBuffer getInbuf() {
155: return this .inbuf;
156: }
157:
158: public ByteBuffer getOutbuf() {
159: return this .outbuf;
160: }
161:
162: public File getInputFile() throws IOException {
163: if (this .fileHandle == null) {
164: this .fileHandle = File.createTempFile("tmp", ".tmp",
165: null);
166: }
167: return this .fileHandle;
168: }
169:
170: public WritableByteChannel getInputChannel() throws IOException {
171: if (this .inputFile == null) {
172: this .inputFile = new RandomAccessFile(getInputFile(),
173: "rw");
174: }
175: if (this .inputChannel == null) {
176: this .inputChannel = this .inputFile.getChannel();
177: }
178: return this .inputChannel;
179: }
180:
181: public void setOutputChannel(final ReadableByteChannel channel) {
182: this .outputChannel = channel;
183: }
184:
185: public ReadableByteChannel getOutputChannel() {
186: return this .outputChannel;
187: }
188:
189: public long getInputCount() {
190: return this .inputCount;
191: }
192:
193: public void incrementInputCount(long count) {
194: this .inputCount += count;
195: }
196:
197: public long getOutputCount() {
198: return this .outputCount;
199: }
200:
201: public void incrementOutputCount(long count) {
202: this .outputCount += count;
203: }
204:
205: public void reset() throws IOException {
206: this .inbuf.clear();
207: this .outbuf.clear();
208: this .inputCount = 0;
209: this .outputCount = 0;
210: if (this .inputChannel != null) {
211: this .inputChannel.close();
212: this .inputChannel = null;
213: }
214: if (this .inputFile != null) {
215: this .inputFile.close();
216: this .inputFile = null;
217: }
218: if (this .fileHandle != null) {
219: this .fileHandle.delete();
220: this .fileHandle = null;
221: }
222: }
223:
224: }
225:
226: public static class FileServiceHandler implements
227: NHttpServiceHandler {
228:
229: private final String docRoot;
230: private boolean useFileChannels;
231: private final HttpParams params;
232: private final HttpResponseFactory responseFactory;
233: private final HttpProcessor httpProcessor;
234: private final ConnectionReuseStrategy connStrategy;
235:
236: public FileServiceHandler(final String docRoot,
237: boolean useFileChannels, final HttpParams params) {
238: super ();
239: this .docRoot = docRoot;
240: this .useFileChannels = useFileChannels;
241: this .params = params;
242: this .responseFactory = new DefaultHttpResponseFactory();
243: this .httpProcessor = createProtocolProcessor();
244: this .connStrategy = new DefaultConnectionReuseStrategy();
245: }
246:
247: private HttpProcessor createProtocolProcessor() {
248: BasicHttpProcessor httpProcessor = new BasicHttpProcessor();
249: httpProcessor.addInterceptor(new ResponseDate());
250: httpProcessor.addInterceptor(new ResponseServer());
251: httpProcessor.addInterceptor(new ResponseContent());
252: httpProcessor.addInterceptor(new ResponseConnControl());
253: return httpProcessor;
254: }
255:
256: public void connected(final NHttpServerConnection conn) {
257: System.out.println("New incoming connection");
258:
259: HttpContext context = conn.getContext();
260: ConnState connState = new ConnState();
261:
262: context.setAttribute(CONN_STATE, connState);
263: }
264:
265: public void closed(final NHttpServerConnection conn) {
266: System.out.println("Connection closed");
267: HttpContext context = conn.getContext();
268:
269: ConnState connState = (ConnState) context
270: .getAttribute(CONN_STATE);
271: try {
272: connState.reset();
273: } catch (IOException ex) {
274: System.err.println("I/O error: " + ex.getMessage());
275: }
276: context.setAttribute(CONN_STATE, null);
277: }
278:
279: public void timeout(final NHttpServerConnection conn) {
280: System.err.println("Timeout");
281: shutdownConnection(conn);
282: }
283:
284: public void exception(final NHttpServerConnection conn,
285: final IOException ex) {
286: System.err.println("I/O error: " + ex.getMessage());
287: shutdownConnection(conn);
288: }
289:
290: public void exception(final NHttpServerConnection conn,
291: final HttpException ex) {
292: if (conn.isResponseSubmitted()) {
293: System.err.println("Unexpected HTTP protocol error: "
294: + ex.getMessage());
295: return;
296: }
297:
298: HttpContext context = conn.getContext();
299:
300: ConnState connState = (ConnState) context
301: .getAttribute(CONN_STATE);
302:
303: HttpResponse response = this .responseFactory
304: .newHttpResponse(HttpVersion.HTTP_1_0,
305: HttpStatus.SC_BAD_REQUEST, context);
306: response.setParams(new DefaultedHttpParams(response
307: .getParams(), this .params));
308:
309: context.setAttribute(ExecutionContext.HTTP_RESPONSE,
310: response);
311: context
312: .setAttribute(ExecutionContext.HTTP_CONNECTION,
313: conn);
314:
315: try {
316: handleException(ex, response, context);
317: this .httpProcessor.process(response, context);
318: commitResponse(conn, connState, response);
319: } catch (HttpException ex2) {
320: shutdownConnection(conn);
321: System.err.println("Unexpected HTTP protocol error: "
322: + ex2.getMessage());
323: } catch (IOException ex2) {
324: shutdownConnection(conn);
325: System.err.println("I/O error: " + ex2.getMessage());
326: }
327: }
328:
329: private void shutdownConnection(final HttpConnection conn) {
330: try {
331: conn.shutdown();
332: } catch (IOException ignore) {
333: }
334: }
335:
336: public void requestReceived(final NHttpServerConnection conn) {
337: HttpContext context = conn.getContext();
338:
339: ConnState connState = (ConnState) context
340: .getAttribute(CONN_STATE);
341:
342: HttpRequest request = conn.getHttpRequest();
343: ProtocolVersion ver = request.getRequestLine()
344: .getProtocolVersion();
345:
346: try {
347: connState.reset();
348:
349: if (request instanceof HttpEntityEnclosingRequest) {
350:
351: HttpEntityEnclosingRequest eeRequest = (HttpEntityEnclosingRequest) request;
352: if (eeRequest.expectContinue()) {
353: HttpResponse ack = this .responseFactory
354: .newHttpResponse(ver, 100, context);
355: conn.submitResponse(ack);
356: }
357: // Wait until the request content is fully received
358: } else {
359: // No request content is expected.
360: // Proceed with service right away
361: doService(conn, connState);
362: }
363: } catch (HttpException ex) {
364: shutdownConnection(conn);
365: System.err.println("Unexpected HTTP protocol error: "
366: + ex.getMessage());
367: } catch (IOException ex) {
368: shutdownConnection(conn);
369: System.err.println("I/O error: " + ex.getMessage());
370: }
371: }
372:
373: public void responseReady(final NHttpServerConnection conn) {
374: }
375:
376: public void inputReady(final NHttpServerConnection conn,
377: final ContentDecoder decoder) {
378: HttpContext context = conn.getContext();
379:
380: ConnState connState = (ConnState) context
381: .getAttribute(CONN_STATE);
382: try {
383:
384: WritableByteChannel channel = connState
385: .getInputChannel();
386: long transferred;
387:
388: // Test if the decoder is capable of direct transfer to file
389: if (this .useFileChannels
390: && decoder instanceof FileContentDecoder
391: && channel instanceof FileChannel) {
392: long pos = connState.getInputCount();
393: transferred = ((FileContentDecoder) decoder)
394: .transfer((FileChannel) channel, pos,
395: Integer.MAX_VALUE);
396: } else {
397: ByteBuffer buf = connState.getInbuf();
398: decoder.read(buf);
399: buf.flip();
400: transferred = channel.write(buf);
401: buf.compact();
402: }
403: connState.incrementInputCount(transferred);
404:
405: if (decoder.isCompleted()) {
406: // Request entity has been fully received
407: channel.close();
408: doService(conn, connState);
409: }
410:
411: } catch (HttpException ex) {
412: shutdownConnection(conn);
413: System.err.println("Unexpected HTTP protocol error: "
414: + ex.getMessage());
415: } catch (IOException ex) {
416: shutdownConnection(conn);
417: System.err.println("I/O error: " + ex.getMessage());
418: }
419: }
420:
421: public void outputReady(final NHttpServerConnection conn,
422: final ContentEncoder encoder) {
423: HttpContext context = conn.getContext();
424:
425: ConnState connState = (ConnState) context
426: .getAttribute(CONN_STATE);
427: HttpResponse response = conn.getHttpResponse();
428: try {
429:
430: ReadableByteChannel channel = connState
431: .getOutputChannel();
432: long transferred;
433:
434: // Test if the encoder is capable of direct transfer from file
435: if (this .useFileChannels
436: && encoder instanceof FileContentEncoder
437: && channel instanceof FileChannel) {
438: long pos = connState.getOutputCount();
439: transferred = ((FileContentEncoder) encoder)
440: .transfer((FileChannel) channel, pos,
441: Integer.MAX_VALUE);
442: } else {
443: ByteBuffer outbuf = connState.getOutbuf();
444: transferred = channel.read(outbuf);
445: if (transferred != -1) {
446: outbuf.flip();
447: encoder.write(outbuf);
448: outbuf.compact();
449: }
450: }
451: if (transferred == -1) {
452: encoder.complete();
453: }
454: if (transferred > 0) {
455: connState.incrementOutputCount(transferred);
456: }
457:
458: if (encoder.isCompleted()) {
459: channel.close();
460: if (!this .connStrategy.keepAlive(response, context)) {
461: conn.close();
462: }
463: }
464:
465: } catch (IOException ex) {
466: shutdownConnection(conn);
467: System.err.println("I/O error: " + ex.getMessage());
468: }
469: }
470:
471: private void commitResponse(final NHttpServerConnection conn,
472: final ConnState connState, final HttpResponse response)
473: throws HttpException, IOException {
474:
475: HttpEntity entity = response.getEntity();
476: if (entity != null) {
477: ReadableByteChannel channel;
478: if (entity instanceof HttpNIOEntity) {
479: channel = ((HttpNIOEntity) entity).getChannel();
480: } else {
481: channel = Channels.newChannel(entity.getContent());
482: }
483: connState.setOutputChannel(channel);
484: }
485: conn.submitResponse(response);
486: }
487:
488: private void doService(final NHttpServerConnection conn,
489: final ConnState connState) throws HttpException,
490: IOException {
491: HttpContext context = conn.getContext();
492: HttpRequest request = conn.getHttpRequest();
493: request.setParams(new DefaultedHttpParams(request
494: .getParams(), this .params));
495:
496: if (request instanceof HttpEntityEnclosingRequest) {
497: HttpEntityEnclosingRequest eeRequest = (HttpEntityEnclosingRequest) request;
498: Header h = request.getFirstHeader(HTTP.CONTENT_TYPE);
499: String contentType = null;
500: if (h != null) {
501: contentType = h.getValue();
502: }
503: HttpNIOEntity entity = new FileNIOEntity(connState
504: .getInputFile(), contentType);
505: eeRequest.setEntity(entity);
506: }
507:
508: ProtocolVersion ver = request.getRequestLine()
509: .getProtocolVersion();
510: HttpResponse response = this .responseFactory
511: .newHttpResponse(ver, 200, context);
512: response.setParams(new DefaultedHttpParams(response
513: .getParams(), this .params));
514:
515: context
516: .setAttribute(ExecutionContext.HTTP_REQUEST,
517: request);
518: context.setAttribute(ExecutionContext.HTTP_RESPONSE,
519: response);
520: context
521: .setAttribute(ExecutionContext.HTTP_CONNECTION,
522: conn);
523:
524: try {
525: this .httpProcessor.process(request, context);
526: handleRequest(request, response, context);
527: } catch (HttpException ex) {
528: handleException(ex, response, context);
529: }
530: this .httpProcessor.process(response, context);
531: commitResponse(conn, connState, response);
532: }
533:
534: private void handleRequest(final HttpRequest request,
535: final HttpResponse response, final HttpContext context) {
536:
537: String target = request.getRequestLine().getUri();
538: File file;
539: try {
540: file = new File(this .docRoot, URLDecoder.decode(target,
541: "UTF-8"));
542: } catch (UnsupportedEncodingException ex) {
543: throw new Error("UTF-8 not supported");
544: }
545:
546: if (!file.exists()) {
547:
548: response.setStatusCode(HttpStatus.SC_NOT_FOUND);
549: byte[] msg = EncodingUtils.getAsciiBytes(file.getName()
550: + ": not found");
551: ByteArrayNIOEntity entity = new ByteArrayNIOEntity(msg);
552: entity.setContentType("text/plain; charset=US-ASCII");
553: response.setEntity(entity);
554:
555: } else if (!file.canRead() || file.isDirectory()) {
556:
557: response.setStatusCode(HttpStatus.SC_FORBIDDEN);
558: byte[] msg = EncodingUtils.getAsciiBytes(file.getName()
559: + ": access denied");
560: ByteArrayNIOEntity entity = new ByteArrayNIOEntity(msg);
561: entity.setContentType("text/plain; charset=US-ASCII");
562: response.setEntity(entity);
563:
564: } else {
565:
566: FileNIOEntity entity = new FileNIOEntity(file,
567: "text/html");
568: response.setEntity(entity);
569:
570: }
571: }
572:
573: private void handleException(final HttpException ex,
574: final HttpResponse response, final HttpContext context) {
575: response.setStatusLine(HttpVersion.HTTP_1_0,
576: HttpStatus.SC_BAD_REQUEST);
577: byte[] msg = EncodingUtils
578: .getAsciiBytes("Malformed HTTP request: "
579: + ex.getMessage());
580: ByteArrayNIOEntity entity = new ByteArrayNIOEntity(msg);
581: entity.setContentType("text/plain; charset=US-ASCII");
582: response.setEntity(entity);
583: }
584:
585: }
586:
587: }
|