[ https://issues.apache.org/jira/browse/HTTPCORE-148?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12570009#action_12570009 ]
Sam Berlin commented on HTTPCORE-148: ------------------------------------- Sounds very on-base. :) I hadn't considered the case where a fully-asynchronous entity would need some time between parsing all the input & committing a response, but you're exactly right -- that's a very probable scenario and will need to be handled. I'm still a little confused with the NHttpRequestHandler#entityContent method. Is that meant to replace ConsumingNHttpEntity.consumeContent? If so, what's the need for NHttpRequestHandler.entityRequest returning a custom ConsumingNHttpEntity? (And if not, what's the purpose of entityContent?) I see what you're saying with regards to ContentListener vs a custom ConsumingNHttpEntity. In my head, I keep bobbing between the two. What I like most about the ContentListener approach is that it's easy to implement. You just have to implement a few methods. With ConsumingNHttpEntity, you have to either subclass an abstract ConsumingNHttpEntity (which should have its constructor set all fields from a passed in entity, which the connection has already set up), or you need to implement all of HttpEntity (which would still need its fields set from the handed-in entity). What about something like this: public interface NHttpRequestHandler { ContentListener entityRequest(HttpEnclosingEntityRequest, HttpContext); void handle(HttpRequest, HttpResponse, NHttpResponseTrigger, HttpContext); } public interface NHttpResponseTrigger { void submit(HttpResponse); void recoverableException(HttpException); void fatalException(IOException); } public interface ConsumingNHttpEntity { public ContentListener getContentListener(); public static interface ContentListener { [stays the same as it is, or maybe a top-level interface] } } With this scheme, the NHttpRequestHandler is given base response object, similar to how HttpRequestHandler is handed one and expected to modify it if necessary. This lets RequestHandlers exist w/o a ResponseFactory, as well as only needing to worry about minor details of status-code & setting a ProducingEntity. Additionally, NHttpResponseTrigger has some more feedback methods, so that it can notify of recoverable exceptions without having to actually construct the "recoverable response" itself, and it can notify of fatal exceptions to close the connection -- these all just provide the functionality that the blocking handlers have. The real change is that entityRequest returns a ContentListener, and that ConsumingNHttpEntity has a getter instead of a setter. The AsyncServiceHandler would use some internal implementation of ConsumingNHttpEntity that has a setter, and would set it based on the handler (or would set a SkipContentListener is no handler exists or the handler chose to ignore the content). I opted for keeping the ContentListener mainly because I really like the ease of implementing, and if ConsumingNHttpEntity exposes a getter, then the 'handle' method still has a way of referencing any data that was stored while read (without having to revert to the keeping it in the context, which I'm not the biggest fan of). It's possible for ConsumingNHttpEntity to also declare 'public void consumeContent(ContentDecoder, IOControl)' & finish(), but in fact it isn't necessary because the Async handler can deal with it internally by using its default implementation. I like keeping the 'entityContent' event out of the handler, because otherwise there could be multiple 'entityContent' events firing in the handler for many different incoming entities, all of which would need to be demultiplexed to something -- it just seems a little strange. As an example, here's what AsyncNHttpFileServer's NHttpRequestHandler would look like: static class HttpFileHandler implements NHttpRequestHandler { private final String docRoot; private final boolean useFileChannels; public HttpFileHandler(final String docRoot, boolean useFileChannels) { this.docRoot = docRoot; this.useFileChannels = useFileChannels; } public ContentListener entityRequest(HttpEntityEnclosingRequest request, HttpContext context) { return new FileWriteListener(useFileChannels); } public void handle( final HttpRequest request, final HttpResponse response, final NHttpResponseTrigger trigger, final HttpContext context) { String target = request.getRequestLine().getUri(); final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8")); if (!file.exists()) { response.setStatusCode(HttpStatus.SC_NOT_FOUND); NStringEntity entity = new NStringEntity("<html><body><h1>File" + file.getPath() + " not found</h1></body></html>", "UTF-8"); entity.setContentType("text/html; charset=UTF-8"); response.setEntity(entity); } else if (!file.canRead() || file.isDirectory()) { response.setStatusCode(HttpStatus.SC_FORBIDDEN); NStringEntity entity = new NStringEntity("<html><body><h1>Access denied</h1></body></html>", "UTF-8"); entity.setContentType("text/html; charset=UTF-8"); response.setEntity(entity); } else { response.setStatusCode(HttpStatus.SC_OK); NFileEntity entity = new NFileEntity(file, "text/html", useFileChannels); response.setEntity(entity); } trigger.submitResponse(response); } } ... and here's an NHttpRequestHandler from the Async tests. HttpRequestHandler requestHandler = new HttpRequestHandler() { public ContentListener entityRequest(HttpEnclosingEntityRequest request, HttpContext context) { return new ByteStorer(); // described below } public void handle( final HttpRequest request, final HttpResponse response, final NHttpResponseTrigger trigger, final HttpContext context) throws HttpException, IOException { if (request instanceof HttpEntityEnclosingRequest) { ByteArrayContentListener storer = (ByteArrayContentListener)((ConsumingNHttpEntity)((HttpEntityEnclosingRequest) request).getEntity()).getContentListener(); response.setEntity(new NByteArrayEntity(storer.getContent())); } else { NStringEntity outgoing = new NStringEntity("No content"); response.setEntity(outgoing); } trigger.submitResponse(response); } }; ByteArrayContentListener implements ContentListener { final SimpleInputBuffer input = new SimpleInputBuffer(2048, new HeapByteBufferAllocator()); @Override public int contentAvailable(ContentDecoder decoder, IOControl ioctrl) throws IOException { return input.consumeContent(decoder); } @Override public void finish() { input.reset(); } byte[] getContent() { byte[] b = new byte[input.length()]; input.read(b); return b; } } ... all those casts to get the get the right kind of ContentListener are a little off-putting, but I do think it's a good tradeoff for not having to implement so many methods just to read an entity. FWIW, the protocol package can provide some common ContentListeners: StringContentListener, ByteArrayContentListener, and FileContentListener. It is kind of similar to the outgoing entities' NByteArrayEntity & pals, but given that incoming entities were never meant to be content-aware, it makes sense. > Create AsyncNHttpServiceHandler & AsyncNHttpClientHandler > --------------------------------------------------------- > > Key: HTTPCORE-148 > URL: https://issues.apache.org/jira/browse/HTTPCORE-148 > Project: HttpComponents Core > Issue Type: New Feature > Components: HttpCore NIO > Affects Versions: 4.0-beta2 > Reporter: Sam Berlin > Assignee: Oleg Kalnichevski > Attachments: changes.txt > > > Attached is a patch for AsyncNHttpServiceHandler. It actually works (as > tested by running & hitting it with IE.) :) > To test, run the example 'AsyncNHttpFileServer' in the examples directory or > the TestAsyncNHttpHandlers test. -- This message is automatically generated by JIRA. - You can reply to this email to add a comment to the issue online. --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]