/***************************************************************************
* Copyright (C) 2008-2011 by Fabrizio Montesi <famontesi@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License as *
* published by the Free Software Foundation; either version 2 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* For details about the authors of this software, see the AUTHORS file. *
***************************************************************************/
package jolie.net;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import jolie.Interpreter;
import jolie.lang.NativeType;
import jolie.net.http.HttpMessage;
import jolie.net.http.HttpParser;
import jolie.net.http.HttpUtils;
import jolie.net.http.json.JsonUtils;
import joliex.gwt.server.JolieGWTConverter;
import jolie.net.http.Method;
import jolie.net.http.MultiPartFormDataParser;
import jolie.net.ports.Interface;
import jolie.net.protocols.CommProtocol;
import jolie.runtime.ByteArray;
import jolie.runtime.Value;
import jolie.runtime.ValueVector;
import jolie.runtime.VariablePath;
import jolie.runtime.typing.OneWayTypeDescription;
import jolie.runtime.typing.RequestResponseTypeDescription;
import jolie.runtime.typing.Type;
import jolie.runtime.typing.TypeCastingException;
import jolie.util.LocationParser;
import jolie.xml.XmlUtils;
import joliex.gwt.client.JolieService;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* HTTP protocol implementation
* @author Fabrizio Montesi
* 14 Nov 2012 - Saverio Giallorenzo - Fabrizio Montesi: support for status codes
*/
public class HttpProtocol extends CommProtocol
{
private static final byte[] NOT_IMPLEMENTED_HEADER = "HTTP/1.1 501 Not Implemented".getBytes();
private static final int DEFAULT_STATUS_CODE = 200;
private static final int DEFAULT_REDIRECTION_STATUS_CODE = 303;
private static final Map< Integer, String > statusCodeDescriptions = new HashMap< Integer, String >();
private static final Set< Integer > locationRequiredStatusCodes = new HashSet< Integer >();
static {
locationRequiredStatusCodes.add( 301 );
locationRequiredStatusCodes.add( 302 );
locationRequiredStatusCodes.add( 303 );
locationRequiredStatusCodes.add( 307 );
locationRequiredStatusCodes.add( 308 );
}
static {
// Initialise the HTTP Status code map.
statusCodeDescriptions.put( 100,"Continue" );
statusCodeDescriptions.put( 101,"Switching Protocols" );
statusCodeDescriptions.put( 102,"Processing" );
statusCodeDescriptions.put( 200,"OK" );
statusCodeDescriptions.put( 201,"Created" );
statusCodeDescriptions.put( 202,"Accepted" );
statusCodeDescriptions.put( 203,"Non-Authoritative Information" );
statusCodeDescriptions.put( 204,"No Content" );
statusCodeDescriptions.put( 205,"Reset Content" );
statusCodeDescriptions.put( 206,"Partial Content" );
statusCodeDescriptions.put( 207,"Multi-Status" );
statusCodeDescriptions.put( 208,"Already Reported" );
statusCodeDescriptions.put( 226,"IM Used" );
statusCodeDescriptions.put( 300,"Multiple Choices" );
statusCodeDescriptions.put( 301,"Moved Permanently" );
statusCodeDescriptions.put( 302,"Found" );
statusCodeDescriptions.put( 303,"See Other" );
statusCodeDescriptions.put( 304,"Not Modified" );
statusCodeDescriptions.put( 305,"Use Proxy" );
statusCodeDescriptions.put( 306,"Reserved" );
statusCodeDescriptions.put( 307,"Temporary Redirect" );
statusCodeDescriptions.put( 308,"Permanent Redirect" );
statusCodeDescriptions.put( 400,"Bad Request" );
statusCodeDescriptions.put( 401,"Unauthorized" );
statusCodeDescriptions.put( 402,"Payment Required" );
statusCodeDescriptions.put( 403,"Forbidden" );
statusCodeDescriptions.put( 404,"Not Found" );
statusCodeDescriptions.put( 405,"Method Not Allowed" );
statusCodeDescriptions.put( 406,"Not Acceptable" );
statusCodeDescriptions.put( 407,"Proxy Authentication Required" );
statusCodeDescriptions.put( 408,"Request Timeout" );
statusCodeDescriptions.put( 409,"Conflict" );
statusCodeDescriptions.put( 410,"Gone" );
statusCodeDescriptions.put( 411,"Length Required" );
statusCodeDescriptions.put( 412,"Precondition Failed" );
statusCodeDescriptions.put( 413,"Request Entity Too Large" );
statusCodeDescriptions.put( 414,"Request-URI Too Long" );
statusCodeDescriptions.put( 415,"Unsupported Media Type" );
statusCodeDescriptions.put( 416,"Requested Range Not Satisfiable" );
statusCodeDescriptions.put( 417,"Expectation Failed" );
statusCodeDescriptions.put( 422,"Unprocessable Entity" );
statusCodeDescriptions.put( 423,"Locked" );
statusCodeDescriptions.put( 424,"Failed Dependency" );
statusCodeDescriptions.put( 426,"Upgrade Required" );
statusCodeDescriptions.put( 427,"Unassigned" );
statusCodeDescriptions.put( 428,"Precondition Required" );
statusCodeDescriptions.put( 429,"Too Many Requests" );
statusCodeDescriptions.put( 430,"Unassigned" );
statusCodeDescriptions.put( 431,"Request Header Fields Too Large" );
statusCodeDescriptions.put( 500,"Internal Server Error" );
statusCodeDescriptions.put( 501,"Not Implemented" );
statusCodeDescriptions.put( 502,"Bad Gateway" );
statusCodeDescriptions.put( 503,"Service Unavailable" );
statusCodeDescriptions.put( 504,"Gateway Timeout" );
statusCodeDescriptions.put( 505,"HTTP Version Not Supported" );
statusCodeDescriptions.put( 507,"Insufficient Storage" );
statusCodeDescriptions.put( 508,"Loop Detected" );
statusCodeDescriptions.put( 509,"Unassigned" );
statusCodeDescriptions.put( 510,"Not Extended" );
statusCodeDescriptions.put( 511,"Network Authentication Required" );
}
private static class Parameters {
private static final String DEBUG = "debug";
private static final String COOKIES = "cookies";
private static final String METHOD = "method";
private static final String ALIAS = "alias";
private static final String MULTIPART_HEADERS = "multipartHeaders";
private static final String CONCURRENT = "concurrent";
private static final String USER_AGENT = "userAgent";
private static final String HEADERS = "headers";
private static final String STATUS_CODE = "statusCode";
private static final String REDIRECT = "redirect";
private static class MultiPartHeaders {
private static final String FILENAME = "filename";
}
}
private static class Headers {
private static String JOLIE_MESSAGE_ID = "X-Jolie-MessageID";
}
private String inputId = null;
private final Transformer transformer;
private final DocumentBuilderFactory docBuilderFactory;
private final DocumentBuilder docBuilder;
private final URI uri;
private final boolean inInputPort;
private MultiPartFormDataParser multiPartFormDataParser = null;
public final static String CRLF = new String( new char[] { 13, 10 } );
public String name()
{
return "http";
}
public boolean isThreadSafe()
{
return checkBooleanParameter( Parameters.CONCURRENT );
}
public HttpProtocol(
VariablePath configurationPath,
URI uri,
boolean inInputPort,
TransformerFactory transformerFactory,
DocumentBuilderFactory docBuilderFactory,
DocumentBuilder docBuilder
)
throws TransformerConfigurationException
{
super( configurationPath );
this.uri = uri;
this.inInputPort = inInputPort;
this.transformer = transformerFactory.newTransformer();
this.docBuilderFactory = docBuilderFactory;
this.docBuilder = docBuilder;
transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
}
private void valueToDocument(
Value value,
Node node,
Document doc
)
{
node.appendChild( doc.createTextNode( value.strValue() ) );
Element currentElement;
for( Entry< String, ValueVector > entry : value.children().entrySet() ) {
if ( !entry.getKey().startsWith( "@" ) ) {
for( Value val : entry.getValue() ) {
currentElement = doc.createElement( entry.getKey() );
node.appendChild( currentElement );
Map< String, ValueVector > attrs = jolie.xml.XmlUtils.getAttributesOrNull( val );
if ( attrs != null ) {
for( Entry< String, ValueVector > attrEntry : attrs.entrySet() ) {
currentElement.setAttribute(
attrEntry.getKey(),
attrEntry.getValue().first().strValue()
);
}
}
valueToDocument( val, currentElement, doc );
}
}
}
}
public String getMultipartHeaderForPart( String operationName, String partName )
{
if ( hasOperationSpecificParameter( operationName, Parameters.MULTIPART_HEADERS ) ) {
Value v = getOperationSpecificParameterFirstValue( operationName, Parameters.MULTIPART_HEADERS );
if ( v.hasChildren( partName ) ) {
v = v.getFirstChild( partName );
if ( v.hasChildren( Parameters.MultiPartHeaders.FILENAME ) ) {
v = v.getFirstChild( Parameters.MultiPartHeaders.FILENAME );
return v.strValue();
}
}
}
return null;
}
private final static String BOUNDARY = "----Jol13H77p$$Bound4r1$$";
private void send_appendCookies( CommMessage message, String hostname, StringBuilder headerBuilder )
{
Value cookieParam = null;
if ( hasOperationSpecificParameter( message.operationName(), Parameters.COOKIES ) ) {
cookieParam = getOperationSpecificParameterFirstValue( message.operationName(), Parameters.COOKIES );
} else if ( hasParameter( Parameters.COOKIES ) ) {
cookieParam = getParameterFirstValue( Parameters.COOKIES );
}
if ( cookieParam != null ) {
Value cookieConfig;
String domain;
StringBuilder cookieSB = new StringBuilder();
for( Entry< String, ValueVector > entry : cookieParam.children().entrySet() ) {
cookieConfig = entry.getValue().first();
if ( message.value().hasChildren( cookieConfig.strValue() ) ) {
domain = cookieConfig.hasChildren( "domain" ) ? cookieConfig.getFirstChild( "domain" ).strValue() : "";
if ( domain.isEmpty() || hostname.endsWith( domain ) ) {
cookieSB
.append( entry.getKey() )
.append( '=' )
.append( message.value().getFirstChild( cookieConfig.strValue() ).strValue() )
.append( ";" );
}
}
}
if ( cookieSB.length() > 0 ) {
headerBuilder
.append( "Cookie: " )
.append( cookieSB )
.append( CRLF );
}
}
}
private void send_appendSetCookieHeader( CommMessage message, StringBuilder headerBuilder )
{
Value cookieParam = null;
if ( hasOperationSpecificParameter( message.operationName(), Parameters.COOKIES ) ) {
cookieParam = getOperationSpecificParameterFirstValue( message.operationName(), Parameters.COOKIES );
} else if ( hasParameter( Parameters.COOKIES ) ) {
cookieParam = getParameterFirstValue( Parameters.COOKIES );
}
if ( cookieParam != null ) {
Value cookieConfig;
for( Entry< String, ValueVector > entry : cookieParam.children().entrySet() ) {
cookieConfig = entry.getValue().first();
if ( message.value().hasChildren( cookieConfig.strValue() ) ) {
headerBuilder
.append( "Set-Cookie: " )
.append( entry.getKey() ).append( '=' )
.append( message.value().getFirstChild( cookieConfig.strValue() ).strValue() )
.append( "; expires=" )
.append( cookieConfig.hasChildren( "expires" ) ? cookieConfig.getFirstChild( "expires" ).strValue() : "" )
.append( "; domain=" )
.append( cookieConfig.hasChildren( "domain" ) ? cookieConfig.getFirstChild( "domain" ).strValue() : "" )
.append( "; path=" )
.append( cookieConfig.hasChildren( "path" ) ? cookieConfig.getFirstChild( "path" ).strValue() : "" );
if ( cookieConfig.hasChildren( "secure" ) && cookieConfig.getFirstChild( "secure" ).intValue() > 0 ) {
headerBuilder.append( "; secure" );
}
headerBuilder.append( CRLF );
}
}
}
}
private String requestFormat = null;
private void send_appendQuerystring( Value value, String charset, StringBuilder headerBuilder )
throws IOException
{
if ( value.children().isEmpty() == false ) {
headerBuilder.append( '?' );
for( Entry< String, ValueVector > entry : value.children().entrySet() ) {
for( Value v : entry.getValue() ) {
headerBuilder
.append( entry.getKey() )
.append( '=' )
.append( URLEncoder.encode( v.strValue(), charset ) )
.append( '&' );
}
}
}
}
private void send_appendParsedAlias( String alias, Value value, String charset, StringBuilder headerBuilder )
throws IOException
{
int offset = 0;
String currStrValue;
String currKey;
StringBuilder result = new StringBuilder( alias );
Matcher m = Pattern.compile( "%(!)?\\{[^\\}]*\\}" ).matcher( alias );
while( m.find() ) {
if ( m.group( 1 ) == null ) { // We have to use URLEncoder
currKey = alias.substring( m.start() + 2, m.end() - 1 );
if ( "$".equals( currKey ) ) {
currStrValue = URLEncoder.encode( value.strValue(), charset );
} else {
currStrValue = URLEncoder.encode( value.getFirstChild( currKey ).strValue(), charset );
}
} else { // We have to insert the string raw
currKey = alias.substring( m.start() + 3, m.end() - 1 );
if ( "$".equals( currKey ) ) {
currStrValue = value.strValue();
} else {
currStrValue = value.getFirstChild( currKey ).strValue();
}
}
result.replace(
m.start() + offset, m.end() + offset,
currStrValue
);
offset += currStrValue.length() - 3 - currKey.length();
}
headerBuilder.append( result );
}
private String getCharset()
{
String charset = "UTF-8";
if ( hasParameter( "charset" ) ) {
charset = getStringParameter( "charset" );
}
return charset;
}
private String send_getFormat()
{
String format = "xml";
if ( inInputPort && requestFormat != null ) {
format = requestFormat;
requestFormat = null;
} else if ( hasParameter( "format" ) ) {
format = getStringParameter( "format" );
}
return format;
}
private static class EncodedContent {
private ByteArray content = null;
private String contentType = "";
private String contentDisposition = "";
}
private EncodedContent send_encodeContent( CommMessage message, Method method, String charset, String format )
throws IOException
{
EncodedContent ret = new EncodedContent();
if ( inInputPort == false && method == Method.GET ) {
// We are building a GET request
return ret;
}
if ( "xml".equals( format ) ) {
Document doc = docBuilder.newDocument();
Element root = doc.createElement( message.operationName() + (( inInputPort ) ? "Response" : "") );
doc.appendChild( root );
if ( message.isFault() ) {
Element faultElement = doc.createElement( message.fault().faultName() );
root.appendChild( faultElement );
valueToDocument( message.fault().value(), faultElement, doc );
} else {
valueToDocument( message.value(), root, doc );
}
Source src = new DOMSource( doc );
ByteArrayOutputStream tmpStream = new ByteArrayOutputStream();
Result dest = new StreamResult( tmpStream );
try {
transformer.transform( src, dest );
} catch( TransformerException e ) {
throw new IOException( e );
}
ret.content = new ByteArray( tmpStream.toByteArray() );
ret.contentType = "text/xml";
} else if ( "binary".equals( format ) ) {
if ( message.value().isByteArray() ) {
ret.content = (ByteArray) message.value().valueObject();
ret.contentType = "application/octet-stream";
}
} else if ( "html".equals( format ) ) {
ret.content = new ByteArray( message.value().strValue().getBytes( charset ) );
ret.contentType = "text/html";
} else if ( "multipart/form-data".equals( format ) ) {
ret.contentType = "multipart/form-data; boundary=" + BOUNDARY;
StringBuilder builder = new StringBuilder();
for( Entry< String, ValueVector > entry : message.value().children().entrySet() ) {
if ( !entry.getKey().startsWith( "@" ) ) {
builder.append( "--" ).append( BOUNDARY ).append( CRLF );
builder.append( "Content-Disposition: form-data; name=\"" ).append( entry.getKey() ).append( '\"' ).append( CRLF ).append( CRLF );
builder.append( entry.getValue().first().strValue() ).append( CRLF );
}
}
builder.append( "--" + BOUNDARY + "--" );
ret.content = new ByteArray( builder.toString().getBytes( charset ) );
} else if ( "x-www-form-urlencoded".equals( format ) ) {
ret.contentType = "application/x-www-form-urlencoded";
Iterator< Entry< String, ValueVector > > it =
message.value().children().entrySet().iterator();
Entry< String, ValueVector > entry;
StringBuilder builder = new StringBuilder();
while( it.hasNext() ) {
entry = it.next();
builder.append( entry.getKey() )
.append( "=" )
.append( URLEncoder.encode( entry.getValue().first().strValue(), "UTF-8" ) );
if ( it.hasNext() ) {
builder.append( '&' );
}
}
ret.content = new ByteArray( builder.toString().getBytes( charset ) );
} else if ( "text/x-gwt-rpc".equals( format ) ) {
ret.contentType = "text/x-gwt-rpc";
try {
if ( message.isFault() ) {
ret.content = new ByteArray(
RPC.encodeResponseForFailure( JolieService.class.getMethods()[0], JolieGWTConverter.jolieToGwtFault( message.fault() ) ).getBytes( charset )
);
} else {
joliex.gwt.client.Value v = new joliex.gwt.client.Value();
JolieGWTConverter.jolieToGwtValue( message.value(), v );
ret.content = new ByteArray(
RPC.encodeResponseForSuccess( JolieService.class.getMethods()[0], v ).getBytes( charset )
);
}
} catch( SerializationException e ) {
throw new IOException( e );
}
} else if ( "json".equals( format ) || "application/json".equals( format ) ) {
ret.contentType = "application/json";
StringBuilder jsonStringBuilder = new StringBuilder();
JsonUtils.valueToJsonString( message.value(), jsonStringBuilder );
ret.content = new ByteArray( jsonStringBuilder.toString().getBytes( charset ) );
}
return ret;
}
private boolean isLocationNeeded( int statusCode )
{
return locationRequiredStatusCodes.contains( statusCode );
}
private void send_appendResponseHeaders( CommMessage message, StringBuilder headerBuilder )
{
int statusCode = DEFAULT_STATUS_CODE;
String statusDescription = null;
if( hasParameter( Parameters.STATUS_CODE ) ) {
statusCode = getIntParameter( Parameters.STATUS_CODE );
if ( !statusCodeDescriptions.containsKey( statusCode ) ) {
Interpreter.getInstance().logWarning( "HTTP protocol for operation " +
message.operationName() +
" is sending a message with status code " +
statusCode +
", which is not in the HTTP specifications."
);
statusDescription = "Internal Server Error";
} else if ( isLocationNeeded( statusCode ) && !hasParameter( Parameters.REDIRECT ) ) {
// if statusCode is a redirection code, location parameter is needed
Interpreter.getInstance().logWarning( "HTTP protocol for operation " +
message.operationName() +
" is sending a message with status code " +
statusCode +
", which expects a redirect parameter but the latter is not set."
);
}
} else if ( hasParameter( Parameters.REDIRECT ) ) {
statusCode = DEFAULT_REDIRECTION_STATUS_CODE;
}
if ( statusDescription == null ) {
statusDescription = statusCodeDescriptions.get( statusCode );
}
headerBuilder.append( "HTTP/1.1 " + statusCode + " " + statusDescription + CRLF );
// if redirect has been set, the redirect location parameter is set
if ( hasParameter( Parameters.REDIRECT ) ) {
headerBuilder.append( "Location: " + getStringParameter( Parameters.REDIRECT ) + CRLF );
}
send_appendSetCookieHeader( message, headerBuilder );
headerBuilder.append( "Server: Jolie" ).append( CRLF );
StringBuilder cacheControlHeader = new StringBuilder();
if ( hasParameter( "cacheControl" ) ) {
Value cacheControl = getParameterFirstValue( "cacheControl" );
if ( cacheControl.hasChildren( "maxAge" ) ) {
cacheControlHeader.append( "max-age=" ).append( cacheControl.getFirstChild( "maxAge" ).intValue() );
}
}
if ( cacheControlHeader.length() > 0 ) {
headerBuilder.append( "Cache-Control: " ).append( cacheControlHeader ).append( CRLF );
}
}
private void send_appendRequestMethod( Method method, StringBuilder headerBuilder )
{
headerBuilder.append( method.id() );
}
private void send_appendRequestPath( CommMessage message, Method method, StringBuilder headerBuilder, String charset )
throws IOException
{
if ( uri.getPath().length() < 1 || uri.getPath().charAt( 0 ) != '/' ) {
headerBuilder.append( '/' );
}
headerBuilder.append( uri.getPath() );
String alias = getOperationSpecificStringParameter( message.operationName(), Parameters.ALIAS );
if ( alias.isEmpty() ) {
headerBuilder.append( message.operationName() );
} else {
send_appendParsedAlias( alias, message.value(), charset, headerBuilder );
}
if ( method == Method.GET ) {
send_appendQuerystring( message.value(), charset, headerBuilder );
}
}
private static void send_appendAuthorizationHeader( CommMessage message, StringBuilder headerBuilder )
{
if ( message.value().hasChildren( jolie.lang.Constants.Predefined.HTTP_BASIC_AUTHENTICATION.token().content() ) ) {
Value v = message.value().getFirstChild( jolie.lang.Constants.Predefined.HTTP_BASIC_AUTHENTICATION.token().content() );
//String realm = v.getFirstChild( "realm" ).strValue();
String userpass =
v.getFirstChild( "userid" ).strValue() + ":" +
v.getFirstChild( "password" ).strValue();
sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
userpass = encoder.encode( userpass.getBytes() );
headerBuilder.append( "Authorization: Basic " ).append( userpass ).append( CRLF );
}
}
private Method send_getRequestMethod( CommMessage message )
throws IOException
{
try {
Method method;
if ( hasOperationSpecificParameter( message.operationName(), Parameters.METHOD ) ) {
method = Method.fromString( getOperationSpecificStringParameter( message.operationName(), Parameters.METHOD ).toUpperCase() );
} else if ( hasParameter( Parameters.METHOD ) ) {
method = Method.fromString( getStringParameter( Parameters.METHOD ).toUpperCase() );
} else {
method = Method.POST;
}
return method;
} catch( Method.UnsupportedMethodException e ) {
throw new IOException( e );
}
}
private void send_appendRequestHeaders( CommMessage message, Method method, StringBuilder headerBuilder, String charset )
throws IOException
{
send_appendRequestMethod( method, headerBuilder );
headerBuilder.append( ' ' );
send_appendRequestPath( message, method, headerBuilder, charset );
headerBuilder.append( " HTTP/1.1" + CRLF );
headerBuilder.append( "Host: " + uri.getHost() + CRLF );
send_appendCookies( message, uri.getHost(), headerBuilder );
send_appendAuthorizationHeader( message, headerBuilder );
}
private void send_appendGenericHeaders(
CommMessage message,
EncodedContent encodedContent,
String charset,
StringBuilder headerBuilder
)
{
String param;
if ( checkBooleanParameter( "keepAlive", true ) == false || channel().toBeClosed() ) {
channel().setToBeClosed( true );
headerBuilder.append( "Connection: close" + CRLF );
}
if ( checkBooleanParameter( Parameters.CONCURRENT, true ) ) {
headerBuilder.append( Headers.JOLIE_MESSAGE_ID ).append( ": " ).append( message.id() ).append( CRLF );
}
if ( encodedContent.content != null ) {
String contentType = getStringParameter( "contentType" );
if ( contentType.length() > 0 ) {
encodedContent.contentType = contentType;
}
headerBuilder.append( "Content-Type: " + encodedContent.contentType );
if ( charset != null ) {
headerBuilder.append( "; charset=" + charset.toLowerCase() );
}
headerBuilder.append( CRLF );
param = getStringParameter( "contentTransferEncoding" );
if ( !param.isEmpty() ) {
headerBuilder.append( "Content-Transfer-Encoding: " + param + CRLF );
}
String contentDisposition = getStringParameter( "contentDisposition" );
if ( contentDisposition.length() > 0 ) {
encodedContent.contentDisposition = contentDisposition;
headerBuilder.append( "Content-Disposition: " + encodedContent.contentDisposition + CRLF );
}
headerBuilder.append( "Content-Length: " + (encodedContent.content.size() + 2) + CRLF );
} else {
headerBuilder.append( "Content-Length: 0" + CRLF );
}
}
private void send_logDebugInfo( CharSequence header, EncodedContent encodedContent )
{
if ( checkBooleanParameter( "debug" ) ) {
StringBuilder debugSB = new StringBuilder();
debugSB.append( "[HTTP debug] Sending:\n" );
debugSB.append( header );
if (
getParameterVector( "debug" ).first().getFirstChild( "showContent" ).intValue() > 0
&& encodedContent.content != null
) {
debugSB.append( encodedContent.content.toString() );
}
Interpreter.getInstance().logInfo( debugSB.toString() );
}
}
public void send( OutputStream ostream, CommMessage message, InputStream istream )
throws IOException
{
Method method = send_getRequestMethod( message );
String charset = getCharset();
String format = send_getFormat();
EncodedContent encodedContent = send_encodeContent( message, method, charset, format );
StringBuilder headerBuilder = new StringBuilder();
if ( inInputPort ) {
// We're responding to a request
send_appendResponseHeaders( message, headerBuilder );
} else {
// We're sending a notification or a solicit
send_appendRequestHeaders( message, method, headerBuilder, charset );
}
send_appendGenericHeaders( message, encodedContent, charset, headerBuilder );
headerBuilder.append( CRLF );
send_logDebugInfo( headerBuilder, encodedContent );
inputId = message.operationName();
/*if ( charset == null ) {
charset = "UTF8";
}*/
ostream.write( headerBuilder.toString().getBytes( charset ) );
if ( encodedContent.content != null ) {
ostream.write( encodedContent.content.getBytes() );
ostream.write( CRLF.getBytes( charset ) );
}
}
private void parseXML( HttpMessage message, Value value )
throws IOException
{
try {
if ( message.size() > 0 ) {
DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource( new ByteArrayInputStream( message.content() ) );
Document doc = builder.parse( src );
XmlUtils.documentToValue( doc, value );
}
} catch( ParserConfigurationException pce ) {
throw new IOException( pce );
} catch( SAXException saxe ) {
throw new IOException( saxe );
}
}
private static void parseJson( HttpMessage message, Value value )
throws IOException
{
JsonUtils.parseJsonIntoValue( new InputStreamReader( new ByteArrayInputStream( message.content() ) ), value );
}
private static void parseForm( HttpMessage message, Value value, String charset )
throws IOException
{
String line = new String( message.content(), "UTF8" );
String[] s, pair;
s = line.split( "&" );
for( int i = 0; i < s.length; i++ ) {
pair = s[i].split( "=", 2 );
value.getChildren( pair[0] ).first().setValue( URLDecoder.decode( pair[1], charset ) );
}
}
private void parseMultiPartFormData( HttpMessage message, Value value )
throws IOException
{
multiPartFormDataParser = new MultiPartFormDataParser( message, value );
multiPartFormDataParser.parse();
}
private static String parseGWTRPC( HttpMessage message, Value value )
throws IOException
{
RPCRequest request = RPC.decodeRequest( new String( message.content(), "UTF8" ) );
String operationName = (String)request.getParameters()[0];
joliex.gwt.client.Value requestValue = (joliex.gwt.client.Value)request.getParameters()[1];
JolieGWTConverter.gwtToJolieValue( requestValue, value );
return operationName;
}
private void recv_checkForSetCookie( HttpMessage message, Value value )
throws IOException
{
if ( hasParameter( Parameters.COOKIES ) ) {
String type;
Value cookies = getParameterFirstValue( Parameters.COOKIES );
Value cookieConfig;
Value v;
for( HttpMessage.Cookie cookie : message.setCookies() ) {
if ( cookies.hasChildren( cookie.name() ) ) {
cookieConfig = cookies.getFirstChild( cookie.name() );
if ( cookieConfig.isString() ) {
v = value.getFirstChild( cookieConfig.strValue() );
if ( cookieConfig.hasChildren( "type" ) ) {
type = cookieConfig.getFirstChild( "type" ).strValue();
} else {
type = "string";
}
recv_assignCookieValue( cookie.value(), v, type );
}
}
/*currValue = Value.create();
currValue.getNewChild( "expires" ).setValue( cookie.expirationDate() );
currValue.getNewChild( "path" ).setValue( cookie.path() );
currValue.getNewChild( "name" ).setValue( cookie.name() );
currValue.getNewChild( "value" ).setValue( cookie.value() );
currValue.getNewChild( "domain" ).setValue( cookie.domain() );
currValue.getNewChild( "secure" ).setValue( (cookie.secure() ? 1 : 0) );
cookieVec.add( currValue );*/
}
}
}
private void recv_assignCookieValue( String cookieValue, Value value, String typeKeyword )
throws IOException
{
NativeType type = NativeType.fromString( typeKeyword );
if ( NativeType.INT == type ) {
try {
value.setValue( new Integer( cookieValue ) );
} catch( NumberFormatException e ) {
throw new IOException( e );
}
} else if ( NativeType.LONG == type ) {
try {
value.setValue( new Long( cookieValue ) );
} catch( NumberFormatException e ) {
throw new IOException( e );
}
} else if ( NativeType.STRING == type ) {
value.setValue( cookieValue );
} else if ( NativeType.DOUBLE == type ) {
try {
value.setValue( new Double( cookieValue ) );
} catch( NumberFormatException e ) {
throw new IOException( e );
}
} else if ( NativeType.BOOL == type ) {
value.setValue( Boolean.valueOf( cookieValue ) );
} else {
value.setValue( cookieValue );
}
}
private void recv_checkForCookies( HttpMessage message, DecodedMessage decodedMessage )
throws IOException
{
Value cookies = null;
if ( hasOperationSpecificParameter( decodedMessage.operationName, Parameters.COOKIES ) ) {
cookies = getOperationSpecificParameterFirstValue( decodedMessage.operationName, Parameters.COOKIES );
} else if ( hasParameter( Parameters.COOKIES ) ) {
cookies = getParameterFirstValue( Parameters.COOKIES );
}
if ( cookies != null ) {
Value v;
String type;
for( Entry< String, String > entry : message.cookies().entrySet() ) {
if ( cookies.hasChildren( entry.getKey() ) ) {
Value cookieConfig = cookies.getFirstChild( entry.getKey() );
if ( cookieConfig.isString() ) {
v = decodedMessage.value.getFirstChild( cookieConfig.strValue() );
if ( cookieConfig.hasChildren( "type" ) ) {
type = cookieConfig.getFirstChild( "type" ).strValue();
} else {
type = "string";
}
recv_assignCookieValue( entry.getValue(), v, type );
}
}
}
}
}
private void recv_checkForGenericHeader( HttpMessage message, DecodedMessage decodedMessage )
throws IOException
{
Value header = null;
if ( hasOperationSpecificParameter( decodedMessage.operationName, Parameters.HEADERS ) ) {
header = getOperationSpecificParameterFirstValue( decodedMessage.operationName, Parameters.HEADERS );
} else if ( hasParameter( Parameters.HEADERS ) ) {
header = getParameterFirstValue( Parameters.HEADERS );
}
if ( header != null ) {
Iterator<String> iterator = header.children().keySet().iterator();
while( iterator.hasNext() ){
String name = iterator.next();
String val = header.getFirstChild( name ).strValue();
name = name.replace( "_", "-" );
decodedMessage.value.getFirstChild(val).setValue(message.getPropertyOrEmptyString(name));
}
}
}
private static void recv_parseQueryString( HttpMessage message, Value value )
{
Map< String, Integer > indexes = new HashMap< String, Integer >();
String queryString = message.requestPath() == null ? "" : message.requestPath();
String[] kv = queryString.split( "\\?" );
Integer index;
if ( kv.length > 1 ) {
queryString = kv[1];
String[] params = queryString.split( "&" );
for( String param : params ) {
kv = param.split( "=", 2 );
if ( kv.length > 1 ) {
index = indexes.get( kv[0] );
if ( index == null ) {
index = 0;
indexes.put( kv[0], index );
}
value.getChildren( kv[0] ).get( index ).setValue( kv[1] );
indexes.put( kv[0], index + 1 );
}
}
}
}
/*
* Prints debug information about a received message
*/
private void recv_logDebugInfo( HttpMessage message )
{
StringBuilder debugSB = new StringBuilder();
debugSB.append( "[HTTP debug] Receiving:\n" );
debugSB.append( "HTTP Code: " + message.statusCode() + "\n" );
debugSB.append( "Resource: " + message.requestPath() + "\n" );
debugSB.append( "--> Header properties\n" );
for( Entry< String, String > entry : message.properties() ) {
debugSB.append( '\t' + entry.getKey() + ": " + entry.getValue() + '\n' );
}
for( HttpMessage.Cookie cookie : message.setCookies() ) {
debugSB.append( "\tset-cookie: " + cookie.toString() + '\n' );
}
for( Entry< String, String > entry : message.cookies().entrySet() ) {
debugSB.append( "\tcookie: " + entry.getKey() + '=' + entry.getValue() + '\n' );
}
if (
getParameterFirstValue( "debug" ).getFirstChild( "showContent" ).intValue() > 0
&& message.content() != null
) {
debugSB.append( "--> Message content\n" );
debugSB.append( new String( message.content() ) );
}
Interpreter.getInstance().logInfo( debugSB.toString() );
}
private void recv_parseRequestFormat( HttpMessage message )
throws IOException
{
requestFormat = null;
String type = message.getPropertyOrEmptyString( "content-type" ).split( ";" )[0];
if ( "text/x-gwt-rpc".equals( type ) ) {
requestFormat = "text/x-gwt-rpc";
} else if ( "application/json".equals( type ) ) {
requestFormat = "application/json";
}
}
private void recv_parseMessage( HttpMessage message, DecodedMessage decodedMessage, String charset )
throws IOException
{
String format = "xml";
if ( hasParameter( "format" ) ) {
format = getStringParameter( "format" );
}
String type = message.getProperty( "content-type" ).split( ";" )[0];
if ( "text/html".equals( type ) ) {
decodedMessage.value.setValue( new String( message.content() ) );
} else if ( "application/x-www-form-urlencoded".equals( type ) ) {
parseForm( message, decodedMessage.value, charset );
} else if ( "text/xml".equals( type ) ) {
parseXML( message, decodedMessage.value );
} else if ( "text/x-gwt-rpc".equals( type ) ) {
decodedMessage.operationName = parseGWTRPC( message, decodedMessage.value );
} else if ( "multipart/form-data".equals( type ) ) {
parseMultiPartFormData( message, decodedMessage.value );
} else if ( "application/octet-stream".equals( type ) || type.startsWith( "image/" ) ) {
decodedMessage.value.setValue( new ByteArray( message.content() ) );
} else if ( "application/json".equals( type ) ) {
parseJson( message, decodedMessage.value );
} else if ( "xml".equals( format ) || "rest".equals( format ) ) {
parseXML( message, decodedMessage.value );
} else if ( "json".equals( format ) ) {
parseJson( message, decodedMessage.value );
} else {
decodedMessage.value.setValue( new String( message.content() ) );
}
}
private void recv_checkReceivingOperation( HttpMessage message, DecodedMessage decodedMessage )
{
if ( decodedMessage.operationName == null ) {
String requestPath = message.requestPath().split( "\\?" )[0];
decodedMessage.operationName = requestPath;
Matcher m = LocationParser.RESOURCE_SEPARATOR_PATTERN.matcher( decodedMessage.operationName );
if ( m.find() ) {
int resourceStart = m.end();
if ( m.find() ) {
decodedMessage.resourcePath = requestPath.substring( resourceStart - 1, m.start() );
decodedMessage.operationName = requestPath.substring( m.end(), requestPath.length() );
}
}
}
if ( decodedMessage.resourcePath.equals( "/" ) && !channel().parentInputPort().canHandleInputOperation( decodedMessage.operationName ) ) {
String defaultOpId = getStringParameter( "default" );
if ( defaultOpId.length() > 0 ) {
Value body = decodedMessage.value;
decodedMessage.value = Value.create();
decodedMessage.value.getChildren( "data" ).add( body );
decodedMessage.value.getFirstChild( "operation" ).setValue( decodedMessage.operationName );
if ( message.userAgent() != null ) {
decodedMessage.value.getFirstChild( Parameters.USER_AGENT ).setValue( message.userAgent() );
}
Value cookies = decodedMessage.value.getFirstChild( "cookies" );
for( Entry< String, String > cookie : message.cookies().entrySet() ) {
cookies.getFirstChild( cookie.getKey() ).setValue( cookie.getValue() );
}
decodedMessage.operationName = defaultOpId;
}
}
}
private void recv_checkForMultiPartHeaders( DecodedMessage decodedMessage )
{
if ( multiPartFormDataParser != null ) {
String target;
for( Entry< String, MultiPartFormDataParser.PartProperties > entry : multiPartFormDataParser.getPartPropertiesSet() ) {
if ( entry.getValue().filename() != null ) {
target = getMultipartHeaderForPart( decodedMessage.operationName, entry.getKey() );
if ( target != null ) {
decodedMessage.value.getFirstChild( target ).setValue( entry.getValue().filename() );
}
}
}
multiPartFormDataParser = null;
}
}
private void recv_checkForMessageProperties( HttpMessage message, DecodedMessage decodedMessage )
throws IOException
{
recv_checkForCookies( message, decodedMessage );
recv_checkForGenericHeader( message, decodedMessage );
recv_checkForMultiPartHeaders( decodedMessage );
if (
message.userAgent() != null &&
hasParameter( Parameters.USER_AGENT )
) {
getParameterFirstValue( Parameters.USER_AGENT ).setValue( message.userAgent() );
}
}
private static class DecodedMessage {
private String operationName = null;
private Value value = Value.create();
private String resourcePath = "/";
private long id = CommMessage.GENERIC_ID;
}
private void recv_checkForStatusCode( HttpMessage message )
{
if ( hasParameter( Parameters.STATUS_CODE ) ) {
getParameterFirstValue( Parameters.STATUS_CODE ).setValue( message.statusCode() );
}
}
public CommMessage recv( InputStream istream, OutputStream ostream )
throws IOException
{
CommMessage retVal = null;
DecodedMessage decodedMessage = new DecodedMessage();
HttpMessage message = new HttpParser( istream ).parse();
if ( message.isSupported() == false ) {
ostream.write( NOT_IMPLEMENTED_HEADER );
ostream.write( CRLF.getBytes() );
ostream.write( CRLF.getBytes() );
ostream.flush();
return null;
}
String charset = getCharset();
if ( message.getProperty( "connection" ) != null ) {
HttpUtils.recv_checkForChannelClosing( message, channel() );
} else {
channel().setToBeClosed( checkBooleanParameter( "keepAlive", true ) == false );
}
if ( checkBooleanParameter( Parameters.DEBUG ) ) {
recv_logDebugInfo( message );
}
recv_checkForStatusCode( message );
recv_parseRequestFormat( message );
if ( message.size() > 0 ) {
recv_parseMessage( message, decodedMessage, charset );
}
if ( checkBooleanParameter( Parameters.CONCURRENT ) ) {
String messageId = message.getProperty( Headers.JOLIE_MESSAGE_ID );
if ( messageId != null ) {
try {
decodedMessage.id = Long.parseLong( messageId );
} catch( NumberFormatException e ) {}
}
}
if ( message.isResponse() ) {
recv_checkForSetCookie( message, decodedMessage.value );
retVal = new CommMessage( decodedMessage.id, inputId, decodedMessage.resourcePath, decodedMessage.value, null );
} else if ( message.isError() == false ) {
if ( message.isGet() ) {
recv_parseQueryString( message, decodedMessage.value );
}
recv_checkReceivingOperation( message, decodedMessage );
recv_checkForMessageProperties( message, decodedMessage );
retVal = new CommMessage( decodedMessage.id, decodedMessage.operationName, decodedMessage.resourcePath, decodedMessage.value, null );
}
if ( "/".equals( retVal.resourcePath() ) && channel().parentPort() != null
&& channel().parentPort().getInterface().containsOperation( retVal.operationName() ) ) {
try {
// The message is for this service
Interface iface = channel().parentPort().getInterface();
OneWayTypeDescription oneWayTypeDescription = iface.oneWayOperations().get( retVal.operationName() );
if ( oneWayTypeDescription != null && message.isResponse() == false ) {
// We are receiving a One-Way message
oneWayTypeDescription.requestType().cast( retVal.value() );
} else {
RequestResponseTypeDescription rrTypeDescription = iface.requestResponseOperations().get( retVal.operationName() );
if ( retVal.isFault() ) {
Type faultType = rrTypeDescription.faults().get( retVal.fault().faultName() );
if ( faultType != null ) {
faultType.cast( retVal.value() );
}
} else {
if ( message.isResponse() ) {
rrTypeDescription.responseType().cast( retVal.value() );
} else {
rrTypeDescription.requestType().cast( retVal.value() );
}
}
}
} catch( TypeCastingException e ) {
// TODO: do something here?
}
}
return retVal;
}
}