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

org.eclipse.jetty.spdy.server.proxy.SPDYProxyEngine Maven / Gradle / Ivy

There is a newer version: 11.0.0.beta1
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//


package org.eclipse.jetty.spdy.server.proxy;

import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.HeadersInfo;
import org.eclipse.jetty.spdy.api.Info;
import org.eclipse.jetty.spdy.api.PushInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.client.SPDYClient;
import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

/**
 * 

{@link SPDYProxyEngine} implements a SPDY to SPDY proxy, that is, converts SPDY events received by clients into * SPDY events for the servers.

*/ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener { private static final Logger LOG = Log.getLogger(SPDYProxyEngine.class); private static final String STREAM_PROMISE_ATTRIBUTE = "org.eclipse.jetty.spdy.server.http.proxy.streamPromise"; private static final String CLIENT_STREAM_ATTRIBUTE = "org.eclipse.jetty.spdy.server.http.proxy.clientStream"; private final ConcurrentMap serverSessions = new ConcurrentHashMap<>(); private final SessionFrameListener sessionListener = new ProxySessionFrameListener(); private final SPDYClient.Factory factory; private volatile long connectTimeout = 15000; private volatile long timeout = 60000; public SPDYProxyEngine(SPDYClient.Factory factory) { this.factory = factory; } public long getConnectTimeout() { return connectTimeout; } public void setConnectTimeout(long connectTimeout) { this.connectTimeout = connectTimeout; } public long getTimeout() { return timeout; } public void setTimeout(long timeout) { this.timeout = timeout; } public StreamFrameListener proxy(final Stream clientStream, SynInfo clientSynInfo, ProxyEngineSelector.ProxyServerInfo proxyServerInfo) { Fields headers = new Fields(clientSynInfo.getHeaders(), false); short serverVersion = getVersion(proxyServerInfo.getProtocol()); InetSocketAddress address = proxyServerInfo.getAddress(); Session serverSession = produceSession(proxyServerInfo.getHost(), serverVersion, address); if (serverSession == null) { rst(clientStream); return null; } final Session clientSession = clientStream.getSession(); addRequestProxyHeaders(clientStream, headers); customizeRequestHeaders(clientStream, headers); convert(clientSession.getVersion(), serverVersion, headers); SynInfo serverSynInfo = new SynInfo(headers, clientSynInfo.isClose()); StreamFrameListener listener = new ProxyStreamFrameListener(clientStream); StreamPromise promise = new StreamPromise(clientStream, serverSynInfo); clientStream.setAttribute(STREAM_PROMISE_ATTRIBUTE, promise); serverSession.syn(serverSynInfo, listener, promise); return this; } private static short getVersion(String protocol) { switch (protocol) { case "spdy/2": return SPDY.V2; case "spdy/3": return SPDY.V3; default: throw new IllegalArgumentException("Procotol: " + protocol + " is not a known SPDY protocol"); } } @Override public StreamFrameListener onPush(Stream stream, PushInfo pushInfo) { throw new IllegalStateException("We shouldn't receive pushes from clients"); } @Override public void onReply(Stream stream, ReplyInfo replyInfo) { throw new IllegalStateException("Servers do not receive replies"); } @Override public void onHeaders(Stream stream, HeadersInfo headersInfo) { // TODO throw new UnsupportedOperationException("Not Yet Implemented"); } @Override public void onData(Stream clientStream, final DataInfo clientDataInfo) { if (LOG.isDebugEnabled()) LOG.debug("C -> P {} on {}", clientDataInfo, clientStream); ByteBufferDataInfo serverDataInfo = new ByteBufferDataInfo(clientDataInfo.asByteBuffer(false), clientDataInfo.isClose()) { @Override public void consume(int delta) { super.consume(delta); clientDataInfo.consume(delta); } }; StreamPromise streamPromise = (StreamPromise)clientStream.getAttribute(STREAM_PROMISE_ATTRIBUTE); streamPromise.data(serverDataInfo); } @Override public void onFailure(Stream stream, Throwable x) { LOG.debug(x); } private Session produceSession(String host, short version, InetSocketAddress address) { try { Session session = serverSessions.get(host); if (session == null) { SPDYClient client = factory.newSPDYClient(version); session = client.connect(address, sessionListener); if (LOG.isDebugEnabled()) LOG.debug("Proxy session connected to {}", address); Session existing = serverSessions.putIfAbsent(host, session); if (existing != null) { session.goAway(new GoAwayInfo(), Callback.Adapter.INSTANCE); session = existing; } } return session; } catch (Exception x) { LOG.debug(x); return null; } } private void convert(short fromVersion, short toVersion, Fields headers) { if (fromVersion != toVersion) { for (HTTPSPDYHeader httpHeader : HTTPSPDYHeader.values()) { Fields.Field header = headers.remove(httpHeader.name(fromVersion)); if (header != null) { String toName = httpHeader.name(toVersion); for (String value : header.getValues()) headers.add(toName, value); } } } } private void rst(Stream stream) { RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM); stream.getSession().rst(rstInfo, Callback.Adapter.INSTANCE); } private class ProxyPushStreamFrameListener implements StreamFrameListener { private PushStreamPromise pushStreamPromise; private ProxyPushStreamFrameListener(PushStreamPromise pushStreamPromise) { this.pushStreamPromise = pushStreamPromise; } @Override public StreamFrameListener onPush(Stream stream, PushInfo pushInfo) { if (LOG.isDebugEnabled()) LOG.debug("S -> P pushed {} on {}. Opening new PushStream P -> C now.", pushInfo, stream); PushStreamPromise newPushStreamPromise = new PushStreamPromise(stream, pushInfo); this.pushStreamPromise.push(newPushStreamPromise); return new ProxyPushStreamFrameListener(newPushStreamPromise); } @Override public void onReply(Stream stream, ReplyInfo replyInfo) { // Push streams never send a reply throw new UnsupportedOperationException(); } @Override public void onHeaders(Stream stream, HeadersInfo headersInfo) { throw new UnsupportedOperationException(); } @Override public void onData(Stream serverStream, final DataInfo serverDataInfo) { if (LOG.isDebugEnabled()) LOG.debug("S -> P pushed {} on {}", serverDataInfo, serverStream); ByteBufferDataInfo clientDataInfo = new ByteBufferDataInfo(serverDataInfo.asByteBuffer(false), serverDataInfo.isClose()) { @Override public void consume(int delta) { super.consume(delta); serverDataInfo.consume(delta); } }; pushStreamPromise.data(clientDataInfo); } @Override public void onFailure(Stream stream, Throwable x) { LOG.debug(x); } } private class ProxyStreamFrameListener extends StreamFrameListener.Adapter { private final Stream receiverStream; public ProxyStreamFrameListener(Stream receiverStream) { this.receiverStream = receiverStream; } @Override public StreamFrameListener onPush(Stream senderStream, PushInfo pushInfo) { if (LOG.isDebugEnabled()) LOG.debug("S -> P {} on {}"); PushInfo newPushInfo = convertPushInfo(pushInfo, senderStream, receiverStream); PushStreamPromise pushStreamPromise = new PushStreamPromise(senderStream, newPushInfo); receiverStream.push(newPushInfo, pushStreamPromise); return new ProxyPushStreamFrameListener(pushStreamPromise); } @Override public void onReply(final Stream stream, ReplyInfo replyInfo) { if (LOG.isDebugEnabled()) LOG.debug("S -> P {} on {}", replyInfo, stream); final ReplyInfo clientReplyInfo = new ReplyInfo(convertHeaders(stream, receiverStream, replyInfo.getHeaders()), replyInfo.isClose()); reply(stream, clientReplyInfo); } private void reply(final Stream stream, final ReplyInfo clientReplyInfo) { receiverStream.reply(clientReplyInfo, new Callback() { @Override public void succeeded() { if (LOG.isDebugEnabled()) LOG.debug("P -> C {} from {} to {}", clientReplyInfo, stream, receiverStream); } @Override public void failed(Throwable x) { LOG.debug(x); rst(receiverStream); } }); } @Override public void onHeaders(Stream stream, HeadersInfo headersInfo) { // TODO throw new UnsupportedOperationException("Not Yet Implemented"); } @Override public void onData(final Stream stream, final DataInfo dataInfo) { if (LOG.isDebugEnabled()) LOG.debug("S -> P {} on {}", dataInfo, stream); data(stream, dataInfo); } private void data(final Stream stream, final DataInfo serverDataInfo) { final ByteBufferDataInfo clientDataInfo = new ByteBufferDataInfo(serverDataInfo.asByteBuffer(false), serverDataInfo.isClose()) { @Override public void consume(int delta) { super.consume(delta); serverDataInfo.consume(delta); } }; receiverStream.data(clientDataInfo, new Callback() //TODO: timeout??? { @Override public void succeeded() { if (LOG.isDebugEnabled()) LOG.debug("P -> C {} from {} to {}", clientDataInfo, stream, receiverStream); } @Override public void failed(Throwable x) { LOG.debug(x); rst(receiverStream); } }); } } /** *

{@link StreamPromise} implements the forwarding of DATA frames from the client to the server and vice * versa.

Instances of this class buffer DATA frames sent by clients and send them to the server. The * buffering happens between the send of the SYN_STREAM to the server (where DATA frames may arrive from the client * before the SYN_STREAM has been fully sent), and between DATA frames, if the client is a fast producer and the * server a slow consumer, or if the client is a SPDY v2 client (and hence without flow control) while the server is * a SPDY v3 server (and hence with flow control).

*/ private class StreamPromise implements Promise { private final Queue queue = new LinkedList<>(); private final Stream senderStream; private final Info info; private Stream receiverStream; private StreamPromise(Stream senderStream, Info info) { this.senderStream = senderStream; this.info = info; } @Override public void succeeded(Stream stream) { if (LOG.isDebugEnabled()) LOG.debug("P -> S {} from {} to {}", info, senderStream, stream); stream.setAttribute(CLIENT_STREAM_ATTRIBUTE, senderStream); DataInfoCallback dataInfoCallback; synchronized (queue) { this.receiverStream = stream; dataInfoCallback = queue.peek(); if (dataInfoCallback != null) { if (dataInfoCallback.flushing) { if (LOG.isDebugEnabled()) LOG.debug("SYN completed, flushing {}, queue size {}", dataInfoCallback.dataInfo, queue.size()); dataInfoCallback = null; } else { dataInfoCallback.flushing = true; if (LOG.isDebugEnabled()) LOG.debug("SYN completed, queue size {}", queue.size()); } } else { if (LOG.isDebugEnabled()) LOG.debug("SYN completed, queue empty"); } } if (dataInfoCallback != null) flush(stream, dataInfoCallback); } @Override public void failed(Throwable x) { LOG.debug(x); rst(senderStream); } public void data(DataInfo dataInfo) { Stream receiverStream; DataInfoCallback dataInfoCallbackToFlush = null; DataInfoCallback dataInfoCallBackToQueue = new DataInfoCallback(dataInfo); synchronized (queue) { queue.offer(dataInfoCallBackToQueue); receiverStream = this.receiverStream; if (receiverStream != null) { dataInfoCallbackToFlush = queue.peek(); if (dataInfoCallbackToFlush.flushing) { if (LOG.isDebugEnabled()) LOG.debug("Queued {}, flushing {}, queue size {}", dataInfo, dataInfoCallbackToFlush.dataInfo, queue.size()); receiverStream = null; } else { dataInfoCallbackToFlush.flushing = true; if (LOG.isDebugEnabled()) LOG.debug("Queued {}, queue size {}", dataInfo, queue.size()); } } else { if (LOG.isDebugEnabled()) LOG.debug("Queued {}, SYN incomplete, queue size {}", dataInfo, queue.size()); } } if (receiverStream != null) flush(receiverStream, dataInfoCallbackToFlush); } private void flush(Stream receiverStream, DataInfoCallback dataInfoCallback) { if (LOG.isDebugEnabled()) LOG.debug("P -> S {} on {}", dataInfoCallback.dataInfo, receiverStream); receiverStream.data(dataInfoCallback.dataInfo, dataInfoCallback); //TODO: timeout??? } private class DataInfoCallback implements Callback { private final DataInfo dataInfo; private boolean flushing; private DataInfoCallback(DataInfo dataInfo) { this.dataInfo = dataInfo; } @Override public void succeeded() { Stream serverStream; DataInfoCallback dataInfoCallback; synchronized (queue) { serverStream = StreamPromise.this.receiverStream; assert serverStream != null; dataInfoCallback = queue.poll(); assert dataInfoCallback == this; dataInfoCallback = queue.peek(); if (dataInfoCallback != null) { assert !dataInfoCallback.flushing; dataInfoCallback.flushing = true; if (LOG.isDebugEnabled()) LOG.debug("Completed {}, queue size {}", dataInfo, queue.size()); } else { if (LOG.isDebugEnabled()) LOG.debug("Completed {}, queue empty", dataInfo); } } if (dataInfoCallback != null) flush(serverStream, dataInfoCallback); } @Override public void failed(Throwable x) { LOG.debug(x); rst(senderStream); } } public Stream getSenderStream() { return senderStream; } public Info getInfo() { return info; } public Stream getReceiverStream() { synchronized (queue) { return receiverStream; } } } private class PushStreamPromise extends StreamPromise { private volatile PushStreamPromise pushStreamPromise; private PushStreamPromise(Stream senderStream, PushInfo pushInfo) { super(senderStream, pushInfo); } @Override public void succeeded(Stream receiverStream) { super.succeeded(receiverStream); if (LOG.isDebugEnabled()) LOG.debug("P -> C PushStreamPromise.succeeded() called with pushStreamPromise: {}", pushStreamPromise); PushStreamPromise promise = pushStreamPromise; if (promise != null) receiverStream.push(convertPushInfo((PushInfo)getInfo(), getSenderStream(), receiverStream), pushStreamPromise); } public void push(PushStreamPromise pushStreamPromise) { Stream receiverStream = getReceiverStream(); if (receiverStream != null) receiverStream.push(convertPushInfo((PushInfo)getInfo(), getSenderStream(), receiverStream), pushStreamPromise); else this.pushStreamPromise = pushStreamPromise; } } private class ProxySessionFrameListener extends SessionFrameListener.Adapter { @Override public void onRst(Session serverSession, RstInfo serverRstInfo) { Stream serverStream = serverSession.getStream(serverRstInfo.getStreamId()); if (serverStream != null) { Stream clientStream = (Stream)serverStream.getAttribute(CLIENT_STREAM_ATTRIBUTE); if (clientStream != null) { Session clientSession = clientStream.getSession(); RstInfo clientRstInfo = new RstInfo(clientStream.getId(), serverRstInfo.getStreamStatus()); clientSession.rst(clientRstInfo, Callback.Adapter.INSTANCE); } } } @Override public void onGoAway(Session serverSession, GoAwayResultInfo goAwayResultInfo) { serverSessions.values().remove(serverSession); } } private PushInfo convertPushInfo(PushInfo pushInfo, Stream from, Stream to) { Fields headersToConvert = pushInfo.getHeaders(); Fields headers = convertHeaders(from, to, headersToConvert); return new PushInfo(getTimeout(), TimeUnit.MILLISECONDS, headers, pushInfo.isClose()); } private Fields convertHeaders(Stream from, Stream to, Fields headersToConvert) { Fields headers = new Fields(headersToConvert, false); addResponseProxyHeaders(from, headers); customizeResponseHeaders(from, headers); convert(from.getSession().getVersion(), to.getSession().getVersion(), headers); return headers; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy