Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [jetty-users] Streaming proxy of content using jetty and jetty-client with end to end flow control

> On Sep 27, 2017, at 2:37 PM, Joakim Erdfelt <joakim@xxxxxxxxxxx> wrote:
> 
> Address list / Mirror list / fallback logic could be entirely within the HttpClient layer, proxy isn't involved there.
> 

Right, I tried to implement that, but found the interfaces too restrictive.  That's why I had to break out of that layer.
In particular, I want to annotate my client Request object with the result of the address transformation.

For example, SocketAddressResolver receives only the host and port -- I need more metadata from the request
to determine the set of InetSocketAddresses to reply with.

Additionally if you rewrite your URI (we use a custom srvc:// protocol) then you quickly start triggering assertions
in HttpDestination.send, where it verifies that your destination scheme / port / host are the same as the request
uri (which is not true in my case, it has been rewritten):

        if (!getScheme().equalsIgnoreCase(request.getScheme()))
            throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this);
        if (!getHost().equalsIgnoreCase(request.getHost()))
            throw new IllegalArgumentException("Invalid request host " + request.getHost() + " for destination " + this);
        int port = request.getPort();
        if (port >= 0 && getPort() != port)
            throw new IllegalArgumentException("Invalid request port " + port + " for destination " + this);

So then I started writing my own Destination type instead, but you can see how the task quickly grows in ways I hadn't expected or intended...

So I'm totally on board with extending HttpClient to do the retries internally but it wasn't quite as easy as I'd hoped :)

> 
> Joakim Erdfelt / joakim@xxxxxxxxxxx
> 
> On Wed, Sep 27, 2017 at 2:22 PM, Steven Schlansker <stevenschlansker@xxxxxxxxx> wrote:
> 
> > On Sep 26, 2017, at 5:05 PM, Joakim Erdfelt <joakim@xxxxxxxxxxx> wrote:
> >
> > modifying the request or response during proxy is also supported out of the box by the ...
> > org.eclipse.jetty.proxy.AsyncMiddleManServlet
> >
> > https://github.com/eclipse/jetty.project/blob/jetty-9.4.x/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java
> >
> 
> This is getting much closer to what I need.  The big thing that's missing, we have a custom
> service resolution protocol layered on top of DNS; I have a list of URIs to attempt
> and if there is a failure before the request content is sent (for example, socket
> timeout, no route to host, connection refused) I then want to try the next in the list
> until we have a success or the operation times out.
> 
> (Once we write the request, we do not do further retries)
> 
> My initial attempt was to implement this as a custom HttpDestination subclass and
> subclass of SocketAddressResolver.Async that returns many InetSocketAddresses;
> unfortunately the resolver does not receive the Destination and similarly the
> Request gets no knowledge of which address ends up getting picked.
> 
> I tried prying open the HttpConversation and HttpExchange objects by the usual blind downcasts,
> but it was seemingly not possible to figure out the actual endpoint selected.
> 
> Not knowing the actual destination means we can't log all the information we want to.
> So my current approach is the tried and true "while (keepTrying) { client.send(...); }"
> which is less elegant but then we know the actual URI we try every time :)
> 
> 
> The MiddleManServlet takes care of the HTTP proxying beautifully, and you are correct
> that I'd rather not replicate that.  But it doesn't look like it does this sort of
> retries, and I'm not sure I can plug in to all the right places to collect the metrics
> and logging data I need.  I'll play with it though and maybe with some cleverly placed
> overrides I can get most of it.
> 
> >
> >
> > Joakim Erdfelt / joakim@xxxxxxxxxxx
> >
> > On Tue, Sep 26, 2017 at 5:03 PM, Steven Schlansker <stevenschlansker@xxxxxxxxx> wrote:
> >
> > > On Sep 26, 2017, at 4:51 PM, Joakim Erdfelt <joakim@xxxxxxxxxxx> wrote:
> > >
> > > This is what our org.eclipse.jetty.proxy.AsyncProxyServlet already does.
> > >
> > > https://github.com/eclipse/jetty.project/blob/jetty-9.4.x/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java
> > >
> > > Why write your own?
> >
> > Because I did not know this existed :)
> >
> > Also, I do nontrivial rewriting of the request (this is not at all a transparent proxy,
> > it is quite invasive) so I will probably not be able to use the code as is.
> >
> > But it probably answers my questions and just needs adaptation, so thank you very much
> > for the pointer.
> >
> > (This is a lot of complex code!  Asynchronous programming is hard...)
> >
> > >
> > >
> > > Joakim Erdfelt / joakim@xxxxxxxxxxx
> > >
> > > On Tue, Sep 26, 2017 at 4:47 PM, Steven Schlansker <stevenschlansker@xxxxxxxxx> wrote:
> > > Hi jetty-users,
> > >
> > > Still working on my http proxy :)
> > > Now I'm trying to make sure I've got the content proxying working correctly
> > > using both asynchronous servlet as well as NIO nonblocking writing.
> > >
> > > Because both the server and client are pushing notifications at me, I believe
> > > I need to implement some sort of transfer queue.  I was hoping that I'd end up
> > > in a push-pull situation but ServletOutputStream calls you via a WriteListener
> > > and Jetty-Client calls you via a Response.ContentListener.
> > >
> > > Unfortunately, this leaves me in a position where I don't know how to get flow
> > > control working.  The client pushes data at me and I don't have a way to push back.
> > >
> > > My current pesudocoded approach:
> > >
> > > Deque<Runnable> writes;
> > > volatile boolean complete = false;
> > >
> > > asyncCtx = httpResponse.startAsync();
> > > out = (HttpOutput) httpResponse.getOutputStream();
> > >
> > > writeListener = new WriteListener() {
> > >     synchronized void onWritePossible() { // avoid re-entrance with 'synchronized'
> > >         while(out.isReady()) {
> > >             pending = writes.removeFirst();
> > >             if (pending != null) {
> > >                 pending.run();
> > >             } else if (complete && writes.isEmpty()) {
> > >                 asyncCtx.complete();
> > >             } else {
> > >                 return;
> > >             }
> > >         }
> > >     }
> > > };
> > >
> > > out.setWriteListener(writeListener);
> > >
> > > proxyClientRequest.send(new Response.ContentListener() {
> > >     void onContent(ByteBuffer buf, Callback complete) {
> > >         writes.push(() -> {
> > >             out.write(buf);
> > >             complete.succeeded();
> > >         };
> > >         writeListener.onWritePossible();  // See if we can write it immediately
> > >     }
> > >
> > >     void onComplete() {
> > >         complete = true;
> > >         writeListener.onWritePossible();
> > >     }
> > > });
> > >
> > > This has the nice property where I don't copy buffers, just keep them in a queue
> > > of pending writes -- but I worry that in the case of a slow reader, Jetty Client
> > > will continue onContent-pushing buffers at me until I run out of memory.
> > >
> > > I could instead use the OutputStreamContentProvider and just read from it in the
> > > WriteListener, which might fix the flow control, but that seems likely to involve
> > > a number of byte[] copies that I was hoping to avoid.
> > >
> > > What's the right approach to wire up Jetty server to Jetty client with full NIO + Async
> > > content proxying, without introducing an intermediate transfer buffer that might grow
> > > without bound?  Am I on the right path or am I missing something?  Thanks for any guidance!
> > >
> > >
> > > _______________________________________________
> > > jetty-users mailing list
> > > jetty-users@xxxxxxxxxxx
> > > To change your delivery options, retrieve your password, or unsubscribe from this list, visit
> > > https://dev.eclipse.org/mailman/listinfo/jetty-users
> > >
> > > _______________________________________________
> > > jetty-users mailing list
> > > jetty-users@xxxxxxxxxxx
> > > To change your delivery options, retrieve your password, or unsubscribe from this list, visit
> > > https://dev.eclipse.org/mailman/listinfo/jetty-users
> >
> >
> > _______________________________________________
> > jetty-users mailing list
> > jetty-users@xxxxxxxxxxx
> > To change your delivery options, retrieve your password, or unsubscribe from this list, visit
> > https://dev.eclipse.org/mailman/listinfo/jetty-users
> >
> > _______________________________________________
> > jetty-users mailing list
> > jetty-users@xxxxxxxxxxx
> > To change your delivery options, retrieve your password, or unsubscribe from this list, visit
> > https://dev.eclipse.org/mailman/listinfo/jetty-users
> 
> 
> _______________________________________________
> jetty-users mailing list
> jetty-users@xxxxxxxxxxx
> To change your delivery options, retrieve your password, or unsubscribe from this list, visit
> https://dev.eclipse.org/mailman/listinfo/jetty-users
> 
> _______________________________________________
> jetty-users mailing list
> jetty-users@xxxxxxxxxxx
> To change your delivery options, retrieve your password, or unsubscribe from this list, visit
> https://dev.eclipse.org/mailman/listinfo/jetty-users

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail


Back to the top