/*
* Copyright 2009 Red Hat, Inc.
*
* Red Hat 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.bayeux;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipelineCoverage;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
/**
* BayeuxDecoder should be used with HTTPDecoder, because it only suppots Bayeux
* protocol transporting on HTTP now. When browser request with Bayeux messages,
* BayuexDecoder only decode and validate them from content of HTTP request.
* Then BayeuxDecoder create or map this request to a BayeuxConnection instance
* and put the valid Bayeux messages to it. At last, BayeuxDecoder throw the
* connection instance to higer layer, by which user can develop their
* application logics.
*
* @author daijun
*/
@ChannelPipelineCoverage("one")
public class BayeuxDecoder extends OneToOneDecoder {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(BayeuxDecoder.class.getName());
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
if (!(msg instanceof HttpRequest)) {
return msg;
}
HttpRequest request = (HttpRequest) msg;
HttpMethod method = request.getMethod();
HttpVersion version = request.getProtocolVersion();
StringBuilder json = new StringBuilder();
StringBuilder jsonp = new StringBuilder();
String paramString = null;
if (HttpMethod.POST == method && HttpVersion.HTTP_1_1 == version && request.getContent().capacity() > 0) {//Callback polling connection type
String charset = "utf-8";//Default unicode char encoding
if (request.containsHeader(HttpHeaders.Names.CONTENT_TYPE)) {
String contentType = request.getHeader(HttpHeaders.Names.CONTENT_TYPE);
charset = contentType.indexOf("charset=") > -1 ? contentType.substring(contentType.indexOf("charset=") + 8) : charset;
charset = isUnicode(charset) ? charset : "utf-8";
}
String httpContent = ((ChannelBuffer) request.getContent()).toString(charset);
logger.debug("HTTP POST: " + httpContent);
paramString = "?" + httpContent;
} else if (HttpMethod.GET == method && request.getUri().length() > 0) {//Callback polling
logger.debug("HTTP GET: " + request.getUri());
paramString = request.getUri();
} else {
return msg;
}
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(paramString);
Map<String, List<String>> paramMaps = queryStringDecoder.getParameters();
if (paramMaps.isEmpty() && HttpMethod.POST == method && HttpVersion.HTTP_1_1 == version) {
json.append(paramString).deleteCharAt(0);
} else if (!paramMaps.isEmpty()) {
if (paramMaps.containsKey("message")) {
for (String param : paramMaps.get("message")) {
json.append("&").append(param);
}
json.deleteCharAt(0);
}
if (paramMaps.containsKey("jsonp")) {
for (String param : paramMaps.get("jsonp")) {
jsonp.append("&").append( param);
}
jsonp.deleteCharAt(0);
}
} else {
return msg;
}
logger.info("Request:" + json);
Object jsonObject = new JSONParser().parse(json.toString());
if (jsonObject == null || !(jsonObject instanceof Object[]) || ((Object[]) jsonObject).length == 0) {
return null;
}
Object[] jsonObjectArray = (Object[]) jsonObject;
BayeuxMessageFactory factory = BayeuxMessageFactory.getInstance();
BayeuxConnection connection = null;
for (Object o : jsonObjectArray) {
BayeuxMessage bayeux = factory.create((Map) o);
connection = BayeuxRouter.getInstance().getConnection(bayeux.clientId);
if (connection == null) {//New client, when handshakeing or publishing withoud connect before
connection = new BayeuxConnection();
connection.setClientAddress(channel.getRemoteAddress());
connection.setServerAddress(channel.getLocalAddress());
connection.setRequestedUri(request.getUri());
String requestedHost = request.containsHeader(HttpHeaders.Names.HOST) ? request.getHeader(HttpHeaders.Names.HOST) : connection.getServerAddress().toString();
connection.setRequestedHost(requestedHost);
} else if (connection.getChannel() != channel) {//Client is polling. Replace the older HTTP connection with the new one.
ConnectResponse[] responses = new ConnectResponse[1];
responses[0] = new ConnectResponse(connection.getClientId(), true);
responses[0].setId(connection.getId());
responses[0].setTimestamp(BayeuxUtil.getCurrentTime());
connection.send(JSONParser.toJSON(responses));
}
connection.setChannel(channel);
connection.setId(bayeux.id);
if (jsonp.length() > 0) {
connection.setJsonp(jsonp.toString());
}
if (HandshakeRequest.isValid(bayeux)) {
connection.putToUpstream(new HandshakeRequest(bayeux));
BayeuxRouter.getInstance().addConnection(connection);
} else if (ConnectRequest.isValid(bayeux)) {
connection.putToUpstream(new ConnectRequest(bayeux));
} else if (DisconnectRequest.isValid(bayeux)) {
connection.putToUpstream(new DisconnectRequest(bayeux));
} else if (SubscribeRequest.isValid(bayeux)) {
connection.putToUpstream(new SubscribeRequest(bayeux));
} else if (UnsubscribeRequest.isValid(bayeux)) {
connection.putToUpstream(new UnsubscribeRequest(bayeux));
} else if (PublishRequest.isValid(bayeux)) {
connection.putToUpstream(new PublishRequest(bayeux));
}
}
return connection;
}
private boolean isUnicode(String charset) {
String unicodes[] = {"utf-8", "utf-16", "utf-16le", "utf-16be", "utf-32", "utf-32le", "utf-32be"};
for (String unicode : unicodes) {
if (unicode.equalsIgnoreCase(charset)) {
return true;
}
}
return false;
}
}