blob: f94823945670de03e81b064719db9b53aebb498b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.asyncweb.client.codec;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.asyncweb.client.auth.AuthScope;
import org.apache.asyncweb.client.auth.AuthState;
import org.apache.asyncweb.client.util.EncodingUtil;
import org.apache.asyncweb.client.util.NameValuePair;
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
/**
* The Class HttpRequestEncoder. This handles the encoding of an {@link HttpRequestMessage} into
* raw bytes.
*/
public class HttpRequestEncoder extends ProtocolEncoderAdapter {
/** The Constant TYPES. */
private static final Set<Class<?>> TYPES;
/** The Constant CRLF. */
private static final String CRLF = "\r\n";
/** The Constant FORM_POST_CONTENT_TYPE. */
private static final String FORM_POST_CONTENT_TYPE = "application/x-www-form-urlencoded";
/** The encoder instances as thread locals. */
private static final ThreadLocal<CharsetEncoder> ENCODER =
new ThreadLocal<CharsetEncoder>() {
@Override
protected CharsetEncoder initialValue() {
return Charset.forName(HttpMessage.HTTP_ELEMENT_CHARSET).newEncoder();
}
};
static {
Set<Class<?>> types = new HashSet<Class<?>>();
types.add(HttpRequestMessage.class);
TYPES = Collections.unmodifiableSet(types);
}
/**
* Instantiates a new http request encoder.
*/
public HttpRequestEncoder() {
}
/**
* Gets the message types for the MINA infrastructure.
*
* @return the message types
*/
public Set<Class<?>> getMessageTypes() {
return TYPES;
}
/**
* Method responsible for encoding a HttpRequestMessage into raw bytes.
*
* @param ioSession the {@link org.apache.mina.common.IoSession} representing the connection to the server.
* @param message the {@link HttpRequestMessage} object
* @param out {@link org.apache.mina.filter.codec.ProtocolEncoderOutput} used for output
* @see org.apache.mina.filter.codec.ProtocolEncoder#encode(org.apache.mina.common.IoSession, java.lang.Object, org.apache.mina.filter.codec.ProtocolEncoderOutput)
*/
public void encode(IoSession ioSession, Object message, ProtocolEncoderOutput out) throws Exception {
HttpRequestMessage msg = (HttpRequestMessage)message;
ByteBuffer buf = ByteBuffer.allocate(1024, false);
// Enable auto-expand for easier encoding
buf.setAutoExpand(true);
StringBuilder sb = new StringBuilder(1024);
//If we have content, lets create the query string
int attrCount = msg.getParameters().size();
String urlAttrs = "";
if (attrCount > 0) {
NameValuePair attrs[] = new NameValuePair[attrCount];
Set<Map.Entry<String, String>> set = msg.getParameters().entrySet();
int i = 0;
for (Map.Entry<String, String> entry : set) {
attrs[i++] = new NameValuePair(entry.getKey(), entry.getValue());
}
urlAttrs = EncodingUtil.formUrlEncode(attrs, msg.getUrlEncodingCharset());
}
String method = msg.getRequestMethod();
sb.append(method).append(' ');
if (method.equals(HttpRequestMessage.REQUEST_CONNECT)) {
sb.append(msg.getHost()).append(':').append(msg.getPort());
} else {
if (msg.isProxyEnabled() && !msg.getProtocol().toLowerCase().equals("https")) {
sb.append(msg.getUrl().toString());
} else {
sb.append(msg.getUrl().getFile());
}
//If its a GET, append the attributes
if (method.equals(HttpRequestMessage.REQUEST_GET) && attrCount > 0) {
//If there is not already a ? in the query, append one, otherwise append a &
if (!msg.getUrl().getFile().contains("?")) {
sb.append('?');
} else {
sb.append('&');
}
sb.append(urlAttrs);
}
}
sb.append(" HTTP/1.1");
sb.append(CRLF);
//This header is required for HTTP/1.1
String hostHeader = msg.getHost();
if ((msg.getProtocol().equals("http") && msg.getPort() != 80)
|| (msg.getProtocol().equals("https") && msg.getPort() != 443)) {
hostHeader += ":" + msg.getPort();
}
// set the host header, removing any Host header that might already exist.
msg.setHeader("Host", hostHeader);
//User agent
if (msg.getUserAgent() != null) {
msg.setHeader("User-Agent", msg.getUserAgent());
}
// potentially for a POST request. We need to obtain the content information
// so we can add the content headers...but the attaching of the content comes later.
byte content[] = null;
// If this is a POST and parameters are provided, this is a form
// post; any existing content is an error and will be ignored and
// the content type will be set accordingly
if (method.equals(HttpRequestMessage.REQUEST_POST) && attrCount > 0) {
content = urlAttrs.getBytes();
// these override any headers that might already be in the set
msg.setHeader(HttpMessage.CONTENT_TYPE, FORM_POST_CONTENT_TYPE);
} else if (msg.getContent() != null) {
// if the message body was provided by the caller, then use it
// (as long as the method is not among the types for which
// entities are disallowed)
if (!method.equals(HttpRequestMessage.REQUEST_TRACE) &&
!method.equals(HttpRequestMessage.REQUEST_CONNECT)) {
content = msg.getContent();
}
}
// set the proper content length header
if (content != null && content.length > 0) {
msg.setHeader(HttpMessage.CONTENT_LENGTH, String.valueOf(content.length));
} else {
// remove any existing content related headers
msg.removeHeader(HttpMessage.CONTENT_TYPE);
msg.removeHeader(HttpMessage.CONTENT_LENGTH);
}
//Process authentication
AuthState state = msg.getAuthState();
if (state != null){
String auth = state.getAuthScheme().authenticate(msg.getCredential(new AuthScope(msg.getHost(), msg.getPort(), state.getAuthScheme().getRealm())),msg);
msg.setHeader("Authorization", auth);
state.setAuthAttempted(true);
}
//Process any headers we have
processHeaders(msg, sb);
//Process cookies
//NOTE: I am just passing the name value pairs and not doing management of the expiration or path
//As that will be left up to the user. A possible enhancement is to make use of a CookieManager
//to handle these issues for the request
processCookies(msg, sb);
//Blank line indicates end of the headers
sb.append(CRLF);
// finally encode and add the string to the buffer
buf.putString(sb, ENCODER.get());
//If this is a POST, then we have content to attach after the blank line
if (content != null) {
buf.put(content);
}
buf.flip();
out.write(buf);
out.flush();
}
/**
* Process header encoding.
*
* @param msg the {@link HttpRequestMessage} message object
* @param sb the <code>StringBuilder</code> at which to append the data
*
* @throws Exception if any exception occurs.
*/
private void processHeaders(HttpRequestMessage msg, StringBuilder sb)
throws Exception {
List<NameValuePair> headers = msg.getHeaders();
for (NameValuePair header : headers) {
String name = header.getName();
String value = header.getValue();
sb.append(name).append(": ").append(value).append(CRLF);
}
//Process authentication
AuthState state = msg.getAuthState();
if (state != null){
String auth = state.getAuthScheme().authenticate(msg.getCredential(new AuthScope(msg.getHost(), msg.getPort(), state.getAuthScheme().getRealm())),msg);
sb.append("Authorization").append(": ").append(auth).append(CRLF);
state.setAuthAttempted(true);
}
}
/**
* Process cookies.
*
* @param msg the msg
* @param sb the StringBuilder
*
* @throws Exception the exception
*/
private void processCookies(HttpRequestMessage msg, StringBuilder sb)
throws Exception {
Collection<Cookie> cookies = msg.getCookies();
if (cookies.size() > 0) {
sb.append("Cookie: ");
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
sb.append(name).append('=').append(value).append("; ");
}
sb.append(CRLF);
}
}
}