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