[ 
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]

Reply via email to