All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.ee10.proxy.AsyncProxyServlet Maven / Gradle / Ivy

There is a newer version: 12.0.13
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.ee10.proxy;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritePendingException;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.AsyncRequestContent;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.client.Response;
import org.eclipse.jetty.server.handler.ConnectHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;

/**
 * 

Servlet 3.1 asynchronous proxy servlet.

*

Both the request processing and the I/O are asynchronous.

* * @see ProxyServlet * @see AsyncMiddleManServlet * @see ConnectHandler */ public class AsyncProxyServlet extends ProxyServlet { private static final String WRITE_LISTENER_ATTRIBUTE = AsyncProxyServlet.class.getName() + ".writeListener"; @Override protected Request.Content proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException { AsyncRequestContent content = new AsyncRequestContent(); request.getInputStream().setReadListener(newReadListener(request, response, proxyRequest, content)); return content; } protected ReadListener newReadListener(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, AsyncRequestContent content) { return new StreamReader(request, response, proxyRequest, content); } @Override protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) { try { if (_log.isDebugEnabled()) _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length); StreamWriter writeListener = (StreamWriter)request.getAttribute(WRITE_LISTENER_ATTRIBUTE); if (writeListener == null) { writeListener = newWriteListener(request, proxyResponse); request.setAttribute(WRITE_LISTENER_ATTRIBUTE, writeListener); // Set the data to write before calling setWriteListener(), because // setWriteListener() may trigger the call to onWritePossible() on // a different thread and we would have a race. writeListener.data(buffer, offset, length, callback); // Setting the WriteListener triggers an invocation to onWritePossible(). response.getOutputStream().setWriteListener(writeListener); } else { writeListener.data(buffer, offset, length, callback); writeListener.onWritePossible(); } } catch (Throwable x) { callback.failed(x); proxyResponse.abort(x); } } protected StreamWriter newWriteListener(HttpServletRequest request, Response proxyResponse) { return new StreamWriter(request, proxyResponse); } /** *

Convenience extension of {@link AsyncProxyServlet} that offers transparent proxy functionalities.

* * @see AbstractProxyServlet.TransparentDelegate */ public static class Transparent extends AsyncProxyServlet { private final TransparentDelegate delegate = new TransparentDelegate(this); @Override public void init(ServletConfig config) throws ServletException { super.init(config); delegate.init(config); } @Override protected String rewriteTarget(HttpServletRequest clientRequest) { return delegate.rewriteTarget(clientRequest); } } protected class StreamReader extends IteratingCallback implements ReadListener { private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()]; private final HttpServletRequest request; private final HttpServletResponse response; private final Request proxyRequest; private final AsyncRequestContent content; protected StreamReader(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, AsyncRequestContent content) { this.request = request; this.response = response; this.proxyRequest = proxyRequest; this.content = content; } @Override public void onDataAvailable() { iterate(); } @Override public void onAllDataRead() { if (_log.isDebugEnabled()) _log.debug("{} proxying content to upstream completed", getRequestId(request)); content.close(); } @Override public void onError(Throwable t) { onClientRequestFailure(request, proxyRequest, response, t); } @Override protected Action process() throws Exception { int requestId = _log.isDebugEnabled() ? getRequestId(request) : 0; ServletInputStream input = request.getInputStream(); while (input.isReady()) { int read = input.read(buffer); if (_log.isDebugEnabled()) _log.debug("{} asynchronous read {} bytes on {}", requestId, read, input); if (read > 0) { if (_log.isDebugEnabled()) _log.debug("{} proxying content to upstream: {} bytes", requestId, read); onRequestContent(request, proxyRequest, content, buffer, 0, read, this); return Action.SCHEDULED; } else if (read < 0) { if (_log.isDebugEnabled()) _log.debug("{} asynchronous read complete on {}", requestId, input); return Action.SUCCEEDED; } } if (_log.isDebugEnabled()) _log.debug("{} asynchronous read pending on {}", requestId, input); return Action.IDLE; } protected void onRequestContent(HttpServletRequest request, Request proxyRequest, AsyncRequestContent content, byte[] buffer, int offset, int length, Callback callback) { content.write(ByteBuffer.wrap(buffer, offset, length), callback); } @Override protected void onCompleteFailure(Throwable cause) { onError(cause); } } protected class StreamWriter implements WriteListener { private final HttpServletRequest request; private final Response proxyResponse; private WriteState state; private byte[] buffer; private int offset; private int length; private Callback callback; protected StreamWriter(HttpServletRequest request, Response proxyResponse) { this.request = request; this.proxyResponse = proxyResponse; this.state = WriteState.IDLE; } protected void data(byte[] bytes, int offset, int length, Callback callback) { if (state != WriteState.IDLE) throw new WritePendingException(); this.state = WriteState.READY; this.buffer = bytes; this.offset = offset; this.length = length; this.callback = callback; } @Override public void onWritePossible() throws IOException { int requestId = getRequestId(request); ServletOutputStream output = request.getAsyncContext().getResponse().getOutputStream(); if (state == WriteState.READY) { // There is data to write. if (_log.isDebugEnabled()) _log.debug("{} asynchronous write start of {} bytes on {}", requestId, length, output); output.write(buffer, offset, length); state = WriteState.PENDING; if (output.isReady()) { if (_log.isDebugEnabled()) _log.debug("{} asynchronous write of {} bytes completed on {}", requestId, length, output); complete(); } else { if (_log.isDebugEnabled()) _log.debug("{} asynchronous write of {} bytes pending on {}", requestId, length, output); } } else if (state == WriteState.PENDING) { // The write blocked but is now complete. if (_log.isDebugEnabled()) _log.debug("{} asynchronous write of {} bytes completing on {}", requestId, length, output); complete(); } else { throw new IllegalStateException(); } } protected void complete() { buffer = null; offset = 0; length = 0; Callback c = callback; callback = null; state = WriteState.IDLE; // Call the callback only after the whole state has been reset, // because the callback may trigger a reentrant call and // the state must already be the new one that we reset here. c.succeeded(); } @Override public void onError(Throwable failure) { proxyResponse.abort(failure); } } private enum WriteState { READY, PENDING, IDLE } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy