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

org.jboss.netty.handler.codec.spdy.SpdyHttpEncoder Maven / Gradle / Ivy

Go to download

The Netty project is an effort to provide an asynchronous event-driven network application framework and tools for rapid development of maintainable high performance and high scalability protocol servers and clients. In other words, Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

There is a newer version: 4.0.0.Alpha8
Show newest version
/*
 * Copyright 2013 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package org.jboss.netty.handler.codec.spdy;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelDownstreamHandler;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.DownstreamMessageEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMessage;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;

import java.net.SocketAddress;
import java.util.List;
import java.util.Map;

import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;

/**
 * Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpChunk}s
 * into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s.
 *
 * 

Request Annotations

* * SPDY specific headers must be added to {@link HttpRequest}s: * * * * * * * * * * * * *
Header NameHeader Value
{@code "X-SPDY-Stream-ID"}The Stream-ID for this request. * Stream-IDs must be odd, positive integers, and must increase monotonically.
{@code "X-SPDY-Priority"}The priority value for this request. * The priority should be between 0 and 7 inclusive. * 0 represents the highest priority and 7 represents the lowest. * This header is optional and defaults to 0.
* *

Response Annotations

* * SPDY specific headers must be added to {@link HttpResponse}s: * * * * * * * * *
Header NameHeader Value
{@code "X-SPDY-Stream-ID"}The Stream-ID of the request corresponding to this response.
* *

Pushed Resource Annotations

* * SPDY specific headers must be added to pushed {@link HttpResponse}s: * * * * * * * * * * * * * * * * * * * * *
Header NameHeader Value
{@code "X-SPDY-Stream-ID"}The Stream-ID for this resource. * Stream-IDs must be even, positive integers, and must increase monotonically.
{@code "X-SPDY-Associated-To-Stream-ID"}The Stream-ID of the request that initiated this pushed resource.
{@code "X-SPDY-Priority"}The priority value for this resource. * The priority should be between 0 and 7 inclusive. * 0 represents the highest priority and 7 represents the lowest. * This header is optional and defaults to 0.
{@code "X-SPDY-URL"}The absolute path for the resource being pushed.
* *

Required Annotations

* * SPDY requires that all Requests and Pushed Resources contain * an HTTP "Host" header. * *

Optional Annotations

* * Requests and Pushed Resources must contain a SPDY scheme header. * This can be set via the {@code "X-SPDY-Scheme"} header but otherwise * defaults to "https" as that is the most common SPDY deployment. * *

Chunked Content

* * This encoder associates all {@link HttpChunk}s that it receives * with the most recently received 'chunked' {@link HttpRequest} * or {@link HttpResponse}. * *

Pushed Resources

* * All pushed resources should be sent before sending the response * that corresponds to the initial request. */ public class SpdyHttpEncoder implements ChannelDownstreamHandler { private final int spdyVersion; private volatile int currentStreamId; /** * Creates a new instance. * * @param spdyVersion the protocol version */ public SpdyHttpEncoder(SpdyVersion spdyVersion) { if (spdyVersion == null) { throw new NullPointerException("spdyVersion"); } this.spdyVersion = spdyVersion.getVersion(); } public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt) throws Exception { if (!(evt instanceof MessageEvent)) { ctx.sendDownstream(evt); return; } MessageEvent e = (MessageEvent) evt; Object msg = e.getMessage(); if (msg instanceof HttpRequest) { HttpRequest httpRequest = (HttpRequest) msg; SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest); currentStreamId = spdySynStreamFrame.getStreamId(); ChannelFuture future = getMessageFuture(ctx, e, currentStreamId, httpRequest); Channels.write(ctx, future, spdySynStreamFrame, e.getRemoteAddress()); } else if (msg instanceof HttpResponse) { HttpResponse httpResponse = (HttpResponse) msg; if (httpResponse.headers().contains(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID)) { SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpResponse); currentStreamId = spdySynStreamFrame.getStreamId(); ChannelFuture future = getMessageFuture(ctx, e, currentStreamId, httpResponse); Channels.write(ctx, future, spdySynStreamFrame, e.getRemoteAddress()); } else { SpdySynReplyFrame spdySynReplyFrame = createSynReplyFrame(httpResponse); currentStreamId = spdySynReplyFrame.getStreamId(); ChannelFuture future = getMessageFuture(ctx, e, currentStreamId, httpResponse); Channels.write(ctx, future, spdySynReplyFrame, e.getRemoteAddress()); } } else if (msg instanceof HttpChunk) { HttpChunk chunk = (HttpChunk) msg; writeChunk(ctx, e.getFuture(), currentStreamId, chunk, e.getRemoteAddress()); } else { // Unknown message type ctx.sendDownstream(evt); } } /** * Writes an HTTP chunk downstream as one or more SPDY frames. */ protected void writeChunk( ChannelHandlerContext ctx, ChannelFuture future, int streamId, HttpChunk chunk, SocketAddress remoteAddress) { if (chunk.isLast()) { if (chunk instanceof HttpChunkTrailer) { HttpChunkTrailer trailer = (HttpChunkTrailer) chunk; HttpHeaders trailers = trailer.trailingHeaders(); if (trailers.isEmpty()) { SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId); spdyDataFrame.setLast(true); Channels.write(ctx, future, spdyDataFrame, remoteAddress); } else { // Create SPDY HEADERS frame out of trailers SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId); spdyHeadersFrame.setLast(true); for (Map.Entry entry: trailers) { spdyHeadersFrame.headers().add(entry.getKey(), entry.getValue()); } Channels.write(ctx, future, spdyHeadersFrame, remoteAddress); } } else { SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId); spdyDataFrame.setLast(true); Channels.write(ctx, future, spdyDataFrame, remoteAddress); } } else { SpdyDataFrame[] spdyDataFrames = createSpdyDataFrames(streamId, chunk.getContent()); ChannelFuture dataFuture = getDataFuture(ctx, future, spdyDataFrames, remoteAddress); // Trigger a write dataFuture.setSuccess(); } } private ChannelFuture getMessageFuture( ChannelHandlerContext ctx, MessageEvent e, int streamId, HttpMessage httpMessage) { if (!httpMessage.getContent().readable()) { return e.getFuture(); } // Create SPDY Data Frames out of message content SpdyDataFrame[] spdyDataFrames = createSpdyDataFrames(streamId, httpMessage.getContent()); if (spdyDataFrames.length > 0) { spdyDataFrames[spdyDataFrames.length - 1].setLast(true); } return getDataFuture(ctx, e.getFuture(), spdyDataFrames, e.getRemoteAddress()); } private static ChannelFuture getDataFuture( ChannelHandlerContext ctx, ChannelFuture future, SpdyDataFrame[] spdyDataFrames, SocketAddress remoteAddress) { ChannelFuture dataFuture = future; for (int i = spdyDataFrames.length; --i >= 0;) { future = Channels.future(ctx.getChannel()); future.addListener(new SpdyFrameWriter(ctx, new DownstreamMessageEvent( ctx.getChannel(), dataFuture, spdyDataFrames[i], remoteAddress))); dataFuture = future; } return dataFuture; } private static class SpdyFrameWriter implements ChannelFutureListener { private final ChannelHandlerContext ctx; private final MessageEvent e; SpdyFrameWriter(ChannelHandlerContext ctx, MessageEvent e) { this.ctx = ctx; this.e = e; } public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { ctx.sendDownstream(e); } else if (future.isCancelled()) { e.getFuture().cancel(); } else { e.getFuture().setFailure(future.getCause()); } } } private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage) throws Exception { boolean chunked = httpMessage.isChunked(); // Get the Stream-ID, Associated-To-Stream-ID, Priority, URL, and scheme from the headers int streamId = SpdyHttpHeaders.getStreamId(httpMessage); int associatedToStreamId = SpdyHttpHeaders.getAssociatedToStreamId(httpMessage); byte priority = SpdyHttpHeaders.getPriority(httpMessage); String URL = SpdyHttpHeaders.getUrl(httpMessage); String scheme = SpdyHttpHeaders.getScheme(httpMessage); SpdyHttpHeaders.removeStreamId(httpMessage); SpdyHttpHeaders.removeAssociatedToStreamId(httpMessage); SpdyHttpHeaders.removePriority(httpMessage); SpdyHttpHeaders.removeUrl(httpMessage); SpdyHttpHeaders.removeScheme(httpMessage); // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding // headers are not valid and MUST not be sent. httpMessage.headers().remove(HttpHeaders.Names.CONNECTION); httpMessage.headers().remove("Keep-Alive"); httpMessage.headers().remove("Proxy-Connection"); httpMessage.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING); SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority); spdySynStreamFrame.setLast(!chunked && !httpMessage.getContent().readable()); // Unfold the first line of the message into name/value pairs if (httpMessage instanceof HttpRequest) { HttpRequest httpRequest = (HttpRequest) httpMessage; SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod()); SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri()); SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion()); } if (httpMessage instanceof HttpResponse) { HttpResponse httpResponse = (HttpResponse) httpMessage; SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus()); SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL); SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion()); spdySynStreamFrame.setUnidirectional(true); } // Replace the HTTP host header with the SPDY host header String host = HttpHeaders.getHost(httpMessage); httpMessage.headers().remove(HttpHeaders.Names.HOST); SpdyHeaders.setHost(spdySynStreamFrame, host); // Set the SPDY scheme header if (scheme == null) { scheme = "https"; } SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme); // Transfer the remaining HTTP headers for (Map.Entry entry: httpMessage.headers()) { spdySynStreamFrame.headers().add(entry.getKey(), entry.getValue()); } return spdySynStreamFrame; } private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse) throws Exception { boolean chunked = httpResponse.isChunked(); // Get the Stream-ID from the headers int streamId = SpdyHttpHeaders.getStreamId(httpResponse); SpdyHttpHeaders.removeStreamId(httpResponse); // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding // headers are not valid and MUST not be sent. httpResponse.headers().remove(HttpHeaders.Names.CONNECTION); httpResponse.headers().remove("Keep-Alive"); httpResponse.headers().remove("Proxy-Connection"); httpResponse.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING); SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId); spdySynReplyFrame.setLast(!chunked && !httpResponse.getContent().readable()); // Unfold the first line of the response into name/value pairs SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus()); SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion()); // Transfer the remaining HTTP headers for (Map.Entry entry: httpResponse.headers()) { spdySynReplyFrame.headers().add(entry.getKey(), entry.getValue()); } return spdySynReplyFrame; } private SpdyDataFrame[] createSpdyDataFrames(int streamId, ChannelBuffer content) { int readableBytes = content.readableBytes(); int count = readableBytes / SPDY_MAX_LENGTH; if (readableBytes % SPDY_MAX_LENGTH > 0) { count++; } SpdyDataFrame[] spdyDataFrames = new SpdyDataFrame[count]; for (int i = 0; i < count; i ++) { SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId); int dataSize = Math.min(content.readableBytes(), SPDY_MAX_LENGTH); spdyDataFrame.setData(content.readSlice(dataSize)); spdyDataFrames[i] = spdyDataFrame; } return spdyDataFrames; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy