blob: 0b9755122efadb9030d0bdd9daf309e97d07eaca [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 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.http2.server;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Executor;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MetaData.Request;
import org.eclipse.jetty.http2.HTTP2Connection;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PrefaceFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.parser.ServerParser;
import org.eclipse.jetty.http2.parser.SettingsBodyParser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ConcurrentArrayQueue;
import org.eclipse.jetty.util.TypeUtil;
public class HTTP2ServerConnection extends HTTP2Connection implements Connection.UpgradeTo
{
private final Queue<HttpChannelOverHTTP2> channels = new ConcurrentArrayQueue<>();
private final ServerSessionListener listener;
private final HttpConfiguration httpConfig;
private final List<Frame> upgradeFrames = new ArrayList<>();
public HTTP2ServerConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, HttpConfiguration httpConfig, ServerParser parser, ISession session, int inputBufferSize, ServerSessionListener listener)
{
super(byteBufferPool, executor, endPoint, parser, session, inputBufferSize);
this.listener = listener;
this.httpConfig = httpConfig;
}
@Override
protected ServerParser getParser()
{
return (ServerParser)super.getParser();
}
@Override
public void onUpgradeTo(ByteBuffer buffer)
{
if (LOG.isDebugEnabled())
LOG.debug("HTTP2 onUpgradeTo {} {}", this, BufferUtil.toDetailString(buffer));
setInputBuffer(buffer);
}
@Override
public void onOpen()
{
notifyAccept(getSession());
for (Frame frame : upgradeFrames)
getSession().onFrame(frame);
super.onOpen();
}
private void notifyAccept(ISession session)
{
try
{
listener.onAccept(session);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
}
}
public void onNewStream(Connector connector, IStream stream, HeadersFrame frame)
{
if (LOG.isDebugEnabled())
LOG.debug("Processing {} on {}", frame, stream);
HttpChannelOverHTTP2 channel = provideHttpChannel(connector, stream);
Runnable task = channel.onRequest(frame);
if (task != null)
offerTask(task, false);
}
public void onData(IStream stream, DataFrame frame, Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("Processing {} on {}", frame, stream);
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
Runnable task = channel.requestContent(frame, callback);
if (task != null)
offerTask(task, false);
}
public void push(Connector connector, IStream stream, MetaData.Request request)
{
if (LOG.isDebugEnabled())
LOG.debug("Processing push {} on {}", request, stream);
HttpChannelOverHTTP2 channel = provideHttpChannel(connector, stream);
Runnable task = channel.onPushRequest(request);
if (task != null)
offerTask(task, true);
}
private HttpChannelOverHTTP2 provideHttpChannel(Connector connector, IStream stream)
{
HttpChannelOverHTTP2 channel = channels.poll();
if (channel != null)
{
channel.getHttpTransport().setStream(stream);
if (LOG.isDebugEnabled())
LOG.debug("Recycling channel {} for {}", channel, this);
}
else
{
HttpTransportOverHTTP2 transport = new HttpTransportOverHTTP2(connector, this);
transport.setStream(stream);
channel = new ServerHttpChannelOverHTTP2(connector, httpConfig, getEndPoint(), transport);
if (LOG.isDebugEnabled())
LOG.debug("Creating channel {} for {}", channel, this);
}
stream.setAttribute(IStream.CHANNEL_ATTRIBUTE, channel);
return channel;
}
public boolean upgrade(Request request)
{
if (HttpMethod.PRI.is(request.getMethod()))
{
getParser().directUpgrade();
}
else
{
HttpField settingsField = request.getFields().getField(HttpHeader.HTTP2_SETTINGS);
if (settingsField == null)
throw new BadMessageException("Missing " + HttpHeader.HTTP2_SETTINGS + " header");
String value = settingsField.getValue();
final byte[] settings = B64Code.decodeRFC4648URL(value == null ? "" : value);
if (LOG.isDebugEnabled())
LOG.debug("{} settings {}",this,TypeUtil.toHexString(settings));
SettingsFrame settingsFrame = SettingsBodyParser.parseBody(BufferUtil.toBuffer(settings));
if (settingsFrame == null)
{
LOG.warn("Invalid {} header value: {}", HttpHeader.HTTP2_SETTINGS, value);
throw new BadMessageException();
}
getParser().standardUpgrade();
upgradeFrames.add(new PrefaceFrame());
upgradeFrames.add(settingsFrame);
// Remember the request to send a response from onOpen().
upgradeFrames.add(new HeadersFrame(1, request, null, true));
}
return true;
}
private class ServerHttpChannelOverHTTP2 extends HttpChannelOverHTTP2
{
public ServerHttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport)
{
super(connector, configuration, endPoint, transport);
}
@Override
public void onCompleted()
{
super.onCompleted();
recycle();
channels.offer(this);
}
}
}