/* * 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.aerogear.io.netty.handler.codec.sockjs.transport; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; import static org.jboss.aerogear.io.netty.handler.codec.sockjs.transport.Transports.CONTENT_TYPE_PLAIN; import static org.jboss.aerogear.io.netty.handler.codec.sockjs.transport.Transports.internalServerErrorResponse; import static org.jboss.aerogear.io.netty.handler.codec.sockjs.transport.Transports.responseWithContent; import com.fasterxml.jackson.core.JsonParseException; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.QueryStringDecoder; import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsConfig; import org.jboss.aerogear.io.netty.handler.codec.sockjs.util.ArgumentUtil; import org.jboss.aerogear.io.netty.handler.codec.sockjs.util.JsonUtil; import io.netty.util.CharsetUtil; import java.util.List; /** * A common base class for SockJS transports that send messages to a SockJS service. */ public abstract class AbstractSendTransport extends SimpleChannelInboundHandler<FullHttpRequest> { protected final SockJsConfig config; protected AbstractSendTransport(final SockJsConfig config) { ArgumentUtil.checkNotNull(config, "config"); this.config = config; } @Override public void messageReceived(final ChannelHandlerContext ctx, final FullHttpRequest request) throws Exception { final String content = getContent(request); if (content.isEmpty()) { ctx.writeAndFlush(internalServerErrorResponse(request.getProtocolVersion(), "Payload expected.")) .addListener(ChannelFutureListener.CLOSE); } else { try { final String[] messages = JsonUtil.decode(content); for (String message : messages) { ctx.fireChannelRead(message); } respond(ctx, request); } catch (final JsonParseException e) { ctx.writeAndFlush(internalServerErrorResponse(request.getProtocolVersion(), "Broken JSON encoding.")); } } } /** * Allows concrete subclasses to very how they will respond after a message has been sent * to the target SockJS service. * Different transport protocols require different responses, for example jsonp_send requires an * OK response while xhr_send NO_CONTENT. * * @param ctx the current {@link ChannelHandlerContext}. * @param request the http request. * @throws Exception if a failure occurs while trying to respond. */ public abstract void respond(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception; private static String getContent(final FullHttpRequest request) { final String contentType = getContentType(request); if (Transports.CONTENT_TYPE_FORM.equals(contentType)) { final List<String> data = getDataFormParameter(request); if (data != null) { return data.get(0); } else { return ""; } } return request.content().toString(CharsetUtil.UTF_8); } private static String getContentType(final FullHttpRequest request) { final String contentType = request.headers().get(CONTENT_TYPE); if (contentType == null) { return CONTENT_TYPE_PLAIN; } return contentType; } private static List<String> getDataFormParameter(final FullHttpRequest request) { final QueryStringDecoder decoder = new QueryStringDecoder('?' + request.content().toString(CharsetUtil.UTF_8)); return decoder.parameters().get("d"); } protected void respond(final ChannelHandlerContext ctx, final HttpVersion httpVersion, final HttpResponseStatus status, final String message) { final FullHttpResponse response = responseWithContent(httpVersion, status, CONTENT_TYPE_PLAIN, message); Transports.setDefaultHeaders(response, config); if (ctx.channel().isActive() && ctx.channel().isRegistered()) { ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } } }