diff options
Diffstat (limited to 'simple/simple-http/src/main')
136 files changed, 27186 insertions, 0 deletions
diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Address.java b/simple/simple-http/src/main/java/org/simpleframework/http/Address.java new file mode 100644 index 0000000..cd05280 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Address.java @@ -0,0 +1,157 @@ +/* + * Address.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import org.simpleframework.common.KeyMap; + +/** + * The <code>Address</code> interface is used to represent a generic + * uniform resource identifier. This interface allows each section + * of the uniform resource identifier to be represented. A generic + * uniform resource identifier syntax is represented in RFC 2616 + * section 3.2.2 for the HTTP protocol, this allows similar URI's + * for example ftp, http, https, tftp. The syntax is + * <code><pre> + * + * URI = [scheme "://"] host [ ":" port ] [ path [ "?" query ]] + * + * </pre></code> + * This interface represents the host, port, path and query part + * of the uniform resource identifier. The parameters are also + * represented by the URI. The parameters in a URI consist of name + * and value pairs in the path segment of the URI. + * <p> + * This will normalize the path part of the uniform resource + * identifier. A normalized path is one that contains no back + * references like "./" and "../". The normalized path will not + * contain the path parameters. + * + * @author Niall Gallagher + */ +public interface Address { + + /** + * This allows the scheme of the URL given to be returned. + * If the URI does not contain a scheme then this will + * return null. The scheme of the URI is the part that + * specifies the type of protocol that the URI is used + * for, an example <code>http://domain/path</code> is + * a URI that is intended for the http protocol. The + * scheme is the string <code>http</code>. + * + * @return the scheme tag for the address if available + */ + String getScheme(); + + /** + * This is used to retrieve the domain of this URI. The + * domain part in the URI is an optional part, an example + * <code>http://domain/path?querypart</code>. This will + * return the value of the domain part. If there is no + * domain part then this will return null otherwise the + * domain value found in the uniform resource identifier. + * + * @return the domain part of the address if available + */ + String getDomain(); + + /** + * This is used to retrieve the port of the uniform resource + * identifier. The port part in this is an optional part, an + * example <code>http://host:port/path?querypart</code>. This + * will return the value of the port. If there is no port then + * this will return <code>-1</code> because this represents + * an impossible uniform resource identifier port. The port + * is an optional part. + * + * @return this returns the port part if it is available + */ + int getPort(); + + /** + * This is used to retrieve the path of this URI. The path part + * is the most fundamental part of the URI. This will return + * the value of the path. If there is no path part then this + * will return a Path implementation that represents the root + * path represented by <code>/</code>. + * + * @return the path part of the uniform resource identifier + */ + Path getPath(); + + /** + * This is used to retrieve the query of this URI. The query part + * in the URI is an optional part. This will return the value + * of the query part. If there is no query part then this will + * return an empty <code>Query</code> object. The query is + * an optional member of a URI and comes after the path part, it + * is preceded by a question mark, <code>?</code> character. + * For example the following URI contains <code>query</code> for + * its query part, <code>http://host:port/path?query</code>. + * <p> + * This returns a <code>org.simpleframework.http.Query</code> + * object that can be used to interact directly with the query + * values. The <code>Query</code> object is a read-only interface + * to the query parameters, and so will not affect the URI. + * + * @return a <code>Query</code> object for the query part + */ + Query getQuery(); + + /** + * This extracts the parameter values from the uniform resource + * identifier represented by this object. The parameters that a + * uniform resource identifier contains are embedded in the path + * part of the URI. If the path contains no parameters then this + * will return an empty <code>Map</code> instance. + * <p> + * This will produce unique name and value parameters. Thus if the + * URI contains several path segments with similar parameter names + * this will return the deepest parameter. For example if the URI + * represented was <code>http://domain/path1;x=y/path2;x=z</code> + * the value for the parameter named <code>x</code> would be + * <code>z</code>. + * + * @return this will return the parameter names found in the URI + */ + KeyMap<String> getParameters(); + + /** + * This is used to convert this URI object into a <code>String</code> + * object. This will only convert the parts of the URI that exist, so + * the URI may not contain the domain or the query part and it will + * not contain the path parameters. If the URI contains all these + * parts then it will return something like + * <pre> + * scheme://host:port/path/path?querypart + * </pre> + * <p> + * It can return <code>/path/path?querypart</code> style relative + * URI's. If any of the parts are set to null then that part will be + * missing, for example if only the path is available then this will + * omit the domain, port and scheme. Showing a relative address. + * <pre> + * scheme://host:port/?querypart + * </pre> + * + * @return the URI address with the optional parts if available + */ + String toString(); +} + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/ContentDisposition.java b/simple/simple-http/src/main/java/org/simpleframework/http/ContentDisposition.java new file mode 100644 index 0000000..579cb91 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/ContentDisposition.java @@ -0,0 +1,59 @@ +/* + * ContentDisposition.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +/** + * The <code>ContentDisposition</code> object represents the HTTP + * Content-Disposition header of a request. A content disposition + * contains the name of the part and whether that part contains + * the contents of a file. If the part represents a parameter then + * the <code>getName</code> can be used to determine the name, if + * it represents a file then <code>getFileName</code> is preferred. + * + * @author Niall Gallagher + */ +public interface ContentDisposition { + + /** + * This method is used to acquire the name of the part. Typically + * this is used when the part represents a text parameter rather + * than a file. However, this can also be used with a file part. + * + * @return this returns the name of the associated part + */ + String getName(); + + /** + * This method is used to acquire the file name of the part. This + * is used when the part represents a text parameter rather than + * a file. However, this can also be used with a file part. + * + * @return this returns the file name of the associated part + */ + String getFileName(); + + /** + * This method is used to determine the type of a part. Typically + * a part is either a text parameter or a file. If this is true + * then the content represented by the associated part is a file. + * + * @return this returns true if the associated part is a file + */ + boolean isFile(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/ContentType.java b/simple/simple-http/src/main/java/org/simpleframework/http/ContentType.java new file mode 100644 index 0000000..c62687d --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/ContentType.java @@ -0,0 +1,142 @@ +/* + * ContentType.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +/** + * This provides access to the MIME type parts, that is the primary + * type, the secondary type and an optional character set parameter. + * The <code>charset</code> parameter is one of many parameters that + * can be associated with a MIME type. This however this exposes this + * parameter with a typed method. + * <p> + * The <code>getCharset</code> will return the character encoding the + * content type is encoded within. This allows the user of the content + * to decode it correctly. Other parameters can be acquired from this + * by simply providing the name of the parameter. + * + * @author Niall Gallagher + */ +public interface ContentType { + + /** + * This method is used to get the primary and secondary parts + * joined together with a "/". This is typically how a content + * type is examined. Here convenience is most important, we can + * easily compare content types without any parameters. + * + * @return this returns the primary and secondary types + */ + String getType(); + + /** + * This sets the primary type to whatever value is in the string + * provided is. If the string is null then this will contain a + * null string for the primary type of the parameter, which is + * likely invalid in most cases. + * + * @param type the type to set for the primary type of this + */ + void setPrimary(String type); + + /** + * This is used to retrieve the primary type of this MIME type. The + * primary type part within the MIME type defines the generic type. + * For example <code>text/plain; charset=UTF-8</code>. This will + * return the text value. If there is no primary type then this + * will return <code>null</code> otherwise the string value. + * + * @return the primary type part of this MIME type + */ + String getPrimary(); + + /** + * This sets the secondary type to whatever value is in the string + * provided is. If the string is null then this will contain a + * null string for the secondary type of the parameter, which is + * likely invalid in most cases. + * + * @param type the type to set for the primary type of this + */ + void setSecondary(String type); + + /** + * This is used to retrieve the secondary type of this MIME type. + * The secondary type part within the MIME type defines the generic + * type. For example <code>text/html; charset=UTF-8</code>. This + * will return the HTML value. If there is no secondary type then + * this will return <code>null</code> otherwise the string value. + * + * @return the primary type part of this MIME type + */ + String getSecondary(); + + /** + * This will set the <code>charset</code> to whatever value the + * string contains. If the string is null then this will not set + * the parameter to any value and the <code>toString</code> method + * will not contain any details of the parameter. + * + * @param charset parameter value to add to the MIME type + */ + void setCharset(String charset); + + /** + * This is used to retrieve the <code>charset</code> of this MIME + * type. This is a special parameter associated with the type, if + * the parameter is not contained within the type then this will + * return null, which typically means the default of ISO-8859-1. + * + * @return the value that this parameter contains + */ + String getCharset(); + + /** + * This is used to retrieve an arbitrary parameter from the MIME + * type header. This ensures that values for <code>boundary</code> + * or other such parameters are not lost when the header is parsed. + * This will return the value, unquoted if required, as a string. + * + * @param name this is the name of the parameter to be retrieved + * + * @return this is the value for the parameter, or null if empty + */ + String getParameter(String name); + + /** + * This will add a named parameter to the content type header. If + * a parameter of the specified name has already been added to the + * header then that value will be replaced by the new value given. + * Parameters such as the <code>boundary</code> as well as other + * common parameters can be set with this method. + * + * @param name this is the name of the parameter to be added + * @param value this is the value to associate with the name + */ + void setParameter(String name, String value); + + /** + * This will return the value of the MIME type as a string. This + * will concatenate the primary and secondary type values and + * add the <code>charset</code> parameter to the type which will + * recreate the content type. + * + * @return this returns the string representation of the type + */ + String toString(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Cookie.java b/simple/simple-http/src/main/java/org/simpleframework/http/Cookie.java new file mode 100644 index 0000000..a9ab422 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Cookie.java @@ -0,0 +1,527 @@ +/* + * Cookie.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.SimpleTimeZone; +import java.util.TimeZone; + +/** + * This class is used to represent a generic cookie. This exposes + * the fields that a cookie can have. By default the version of the + * <code>Cookie</code> is set to 1. The version can be configured + * using the <code>setVersion</code> method. The domain, path, + * security, and expiry of the cookie can also be set using their + * respective set methods. + * <p> + * The <code>toString</code> method allows the <code>Cookie</code> + * to be converted back into text form. This text form converts the + * cookie according to the Set-Cookie header form. This is done so + * that a created <code>Cookie</code> instance can be converted + * to a string which can be used as a a HTTP header. + * + * @author Niall Gallagher + */ +public class Cookie { + + /** + * This is used to set the expiry date for the cookie. + */ + private CookieDate date; + + /** + * The name attribute of this cookie instance. + */ + private String name; + + /** + * The value attribute of this cookie instance. + */ + private String value; + + /** + * Represents the value of the path for this cookie. + */ + private String path; + + /** + * Represents the value of the domain attribute. + */ + private String domain; + + /** + * Determines whether the cookie should be secure. + */ + private boolean secure; + + /** + * Determines whether the cookie should be protected. + */ + private boolean protect; + + /** + * This is used to determine the the cookie is new. + */ + private boolean created; + + /** + * Represents the value of the version attribute. + */ + private int version; + + /** + * Represents the duration in seconds of the cookie. + */ + private int expiry; + + /** + * Constructor of the <code>Cookie</code> that uses a default + * version of 1, which is used by RFC 2109. This contains none + * of the optional attributes, such as domain and path. These + * optional attributes can be set using the set methods. + * <p> + * The name must conform to RFC 2109, which means that it can + * contain only ASCII alphanumeric characters and cannot have + * commas, white space, or semicolon characters. + * + * @param name this is the name of this cookie instance + * @param value this is the value of this cookie instance + */ + public Cookie(String name, String value) { + this(name, value, "/"); + } + + /** + * Constructor of the <code>Cookie</code> that uses a default + * version of 1, which is used by RFC 2109. This contains none + * of the optional attributes, such as domain and path. These + * optional attributes can be set using the set methods. + * <p> + * The name must conform to RFC 2109, which means that it can + * contain only ASCII alphanumeric characters and cannot have + * commas, white space, or semicolon characters. + * + * @param name this is the name of this cookie instance + * @param value this is the value of this cookie instance + * @param created this determines if the cookie is new + */ + public Cookie(String name, String value, boolean created) { + this(name, value, "/", created); + } + + /** + * Constructor of the <code>Cookie</code> that uses a default + * version of 1, which is used by RFC 2109. This allows the + * path attribute to be specified for on construction. Other + * attributes can be set using the set methods provided. + * <p> + * The name must conform to RFC 2109, which means that it can + * contain only ASCII alphanumeric characters and cannot have + * commas, white space, or semicolon characters. + * + * @param name this is the name of this cookie instance + * @param value this is the value of this cookie instance + * @param path the path attribute of this cookie instance + */ + public Cookie(String name, String value, String path) { + this(name, value, path, false); + } + + /** + * Constructor of the <code>Cookie</code> that uses a default + * version of 1, which is used by RFC 2109. This allows the + * path attribute to be specified for on construction. Other + * attributes can be set using the set methods provided. + * <p> + * The name must conform to RFC 2109, which means that it can + * contain only ASCII alphanumeric characters and cannot have + * commas, white space, or semicolon characters. + * + * @param name this is the name of this cookie instance + * @param value this is the value of this cookie instance + * @param path the path attribute of this cookie instance + * @param created this determines if the cookie is new + */ + public Cookie(String name, String value, String path, boolean created) { + this.date = new CookieDate(); + this.created = created; + this.value = value; + this.name = name; + this.path = path; + this.version = 1; + this.expiry = -1; + } + + /** + * This is used to determine if the cookie is new. A cookie is + * considered new if it has just been created on the server. A + * cookie is considered not new if it has been received by the + * client in a request. This allows the server to determine if + * the cookie needs to be delivered to the client. + * + * @return this returns true if the cookie was just created + */ + public boolean isNew() { + return created; + } + + /** + * This returns the version for this cookie. The version is + * not optional and so will always return the version this + * cookie uses. If no version number is specified this will + * return a version of 1, to comply with RFC 2109. + * + * @return the version value from this cookie instance + */ + public int getVersion() { + return version; + } + + /** + * This enables the version of the <code>Cookie</code> to be + * set. By default the version of the <code>Cookie</code> is + * set to 1. It is not advisable to set the version higher + * than 1, unless it is known that the client will accept it. + * <p> + * Some old browsers can only handle cookie version 0. This + * can be used to comply with the original Netscape cookie + * specification. Version 1 complies with RFC 2109. + * + * @param version this is the version number for the cookie + */ + public void setVersion(int version) { + this.version = version; + } + + /** + * This returns the name for this cookie. The name and value + * attributes of a cookie define what the <code>Cookie</code> + * is for, these values will always be present. These are + * mandatory for both the Cookie and Set-Cookie headers. + * <p> + * Because the cookie may be stored by name, the cookie name + * cannot be modified after the creation of the cookie object. + * + * @return the name from this cookie instance object + */ + public String getName() { + return name; + } + + /** + * This returns the value for this cookie. The name and value + * attributes of a cookie define what the <code>Cookie</code> + * is for, these values will always be present. These are + * mandatory for both the Cookie and Set-Cookie headers. + * + * @return the value from this cookie instance object + */ + public String getValue() { + return value; + } + + /** + * This enables the value of the cookie to be changed. This + * can be set to any value the server wishes to send. Cookie + * values can contain space characters as they are transmitted + * in quotes. For example a value of <code>some value</code> + * is perfectly legal. However for maximum compatibility + * across the different plaforms such as PHP, JavaScript and + * others, quotations should be avoided. If quotations are + * required they must be added to the string. For example a + * quoted value could be created as <code>"some value"</code>. + * + * @param value this is the new value of this cookie object + */ + public void setValue(String value) { + this.value = value; + } + + /** + * This determines whether the cookie is secure. The cookie + * is secure if it has the "secure" token set, as defined + * by RFC 2109. If this token is set then the cookie is only + * sent over secure channels such as SSL and TLS and ensures + * that a third party cannot intercept and spoof the cookie. + * + * @return this returns true if the "secure" token is set + */ + public boolean isSecure() { + return secure; + } + + /** + * This is used to determine if the client browser should send + * this cookie over a secure protocol. If this is true then + * the client browser should only send the cookie over secure + * channels such as SSL and TLS. This ensures that the value + * of the cookie cannot be intercepted by a third party. + * + * @param secure if true then the cookie should be secure + */ + public void setSecure(boolean secure) { + this.secure = secure; + } + + /** + * This is used to determine if the cookie is protected against + * cross site scripting. It sets the <code>HttpOnly</code> value + * for the cookie. Setting this value ensures that the cookie + * is not available to some scripting attacks. + * + * @return this returns true if the cookie is protected + */ + public boolean isProtected() { + return protect; + } + + /** + * This is used to protect the cookie from cross site scripting + * vulnerabilities. If this is set to true the cookie will be + * protected by setting the <code>HttpOnly</code> value for the + * cookie. See RFC 6265 for more details on this value. + * + * @param protect this determines if the cookie is protected + */ + public void setProtected(boolean protect) { + this.protect = protect; + } + + /** + * This returns the number of seconds a cookie lives for. This + * determines how long the cookie will live on the client side. + * If the expiry is less than zero the cookie lifetime is the + * duration of the client browser session, if it is zero then + * the cookie will be deleted from the client browser. + * + * @return returns the duration in seconds the cookie lives + */ + public int getExpiry() { + return expiry; + } + + /** + * This allows a lifetime to be specified for the cookie. This + * will make use of the "max-age" token specified by RFC 2109 + * the specifies the number of seconds a browser should keep + * a cookie for. This is useful if the cookie is to be kept + * beyond the lifetime of the client session. If the value of + * this is zero then this will remove the client cookie, if + * it is less than zero then the "max-age" field is ignored. + * + * @param expiry the duration in seconds the cookie lives + */ + public void setExpiry(int expiry){ + this.expiry = expiry; + } + + /** + * This returns the path for this cookie. The path is in both + * the Cookie and Set-Cookie headers and so may return null + * if there is no domain value. If the <code>toString</code> + * or <code>toClientString</code> is invoked the path will + * not be present if the path attribute is null. + * + * @return this returns the path value from this cookie + */ + public String getPath() { + return path; + } + + /** + * This is used to set the cookie path for this cookie. This + * is set so that the cookie can specify the directories that + * the cookie is sent with. For example if the path attribute + * is set to <code>/pub/bin</code>, then requests for the + * resource <code>http://hostname:port/pub/bin/README</code> + * will be issued with this cookie. The cookie is issued for + * all resources in the path and all subdirectories. + * + * @param path this is the path value for this cookie object + */ + public void setPath(String path) { + this.path = path; + } + + /** + * This returns the domain for this cookie. The domain is in + * both the Cookie and Set-Cookie headers and so may return + * null if there is no domain value. If either the + * <code>toString</code> or <code>toClientString</code> is + * invoked the domain will not be present if this is null. + * + * @return this returns the domain value from this cookie + */ + public String getDomain() { + return domain; + } + + /** + * This enables the domain for this <code>Cookie</code> to be + * set. The form of the domain is specified by RFC 2109. The + * value can begin with a dot, like <code>.host.com</code>. + * This means that the cookie is visible within a specific + * DNS zone like <code>www.host.com</code>. By default this + * value is null which means it is sent back to its origin. + * + * @param domain this is the domain value for this cookie + */ + public void setDomain(String domain) { + this.domain = domain; + } + + /** + * This will give the correct string value of this cookie. This + * will generate the cookie text with only the values that were + * given with this cookie. If there are no optional attributes + * like $Path or $Domain these are left blank. This returns the + * encoding as it would be for the HTTP Cookie header. + * + * @return this returns the Cookie header encoding of this + */ + public String toClientString(){ + return "$Version="+version+"; "+name+"="+ + value+ (path==null?"":"; $Path="+ + path)+ (domain==null? "":"; $Domain="+ + domain); + } + + /** + * The <code>toString</code> method converts the cookie to the + * Set-Cookie value. This can be used to send the HTTP header + * to a client browser. This uses a format that has been tested + * with various browsers. This is required as some browsers + * do not perform flexible parsing of the Set-Cookie value. + * <p> + * Netscape and IE-5.0 can't or wont handle <code>Path</code> + * it must be <code>path</code> also Netscape can not handle + * the path in quotations such as <code>"/path"</code> it must + * be <code>/path</code>. This value is never in quotations. + * <p> + * For maximum compatibility cookie values are not transmitted + * in quotations. This is done to ensure that platforms like + * PHP, JavaScript and various others that don't comply with + * RFC 2109 can transparently access the sent cookies. + * <p> + * When setting the expiry time for the cookie it is important + * to set the <code>max-age</code> and <code>expires</code> + * attributes so that IE-5.0 and up can understand them. Old + * versions of IE do not understand <code>max-age</code>. + * + * @return this returns a Set-Cookie encoding of the cookie + */ + public String toString(){ + return name+"="+value+"; version="+ + version +(path ==null ?"":"; path="+path)+ + (domain ==null ?"": "; domain="+domain)+ + (expiry< 0?"":"; expires="+date.format(expiry))+ + (expiry < 0 ? "" : "; max-age="+expiry)+ + (secure ? "; secure" : "") + + (protect ? "; httponly" : ""); + + } + + /** + * The <code>CookieDate</code> complies with the date format + * used by older browsers such as Internet Explorer and + * Netscape Navigator. The format of the date is not the same + * as other HTTP date headers. It takes the form. + * <pre> + * + * DAY, DD-MMM-YYYY HH:MM:SS GMT + * + * </pre> + * Support for this format is required as many browsers do + * not support <code>max-age</code> and so cookies will not + * expire for these browsers. + */ + private static class CookieDate { + + /** + * This is the format that is required for the date. + */ + private static final String FORMAT = "EEE, dd-MMM-yyyy HH:mm:ss z"; + + /** + * The cookie date must be returned in the GMT zone. + */ + private static final String ZONE = "GMT"; + + /** + * This is the date formatter used to build the string. + */ + private final DateFormat format; + + /** + * This is the GMT time zone which must be used. + */ + private final TimeZone zone; + + /** + * Constructor for the <code>CookieDate</code> formatter. + * This creates the time zone and date formatting tools + * that are need to convert the expiry in seconds to the + * correct text format for older browsers to understand. + */ + public CookieDate() { + this.format = new SimpleDateFormat(FORMAT); + this.zone = new SimpleTimeZone(0, ZONE); + } + + /** + * This takes the number of seconds the cookie will live + * for. In order for this to be respected by older browsers + * such as IE-5.0 to IE-9.0 this must return a string in + * the original cookie specification by Netscape. + * + * @param seconds the number of seconds from now + * + * @return a date formatted for used with old browsers + */ + public String format(int seconds) { + Calendar calendar = Calendar.getInstance(zone); + Date date = convert(seconds); + + calendar.setTime(date); + format.setCalendar(calendar); + + return format.format(date); + } + + /** + * This method is used to convert the provided time to + * a date that can be formatted. The time returned is the + * current time plus the number of seconds provided. + * + * @param seconds the number of seconds from now + * + * @return a date representing some time in the future + */ + private Date convert(int seconds) { + long now = System.currentTimeMillis(); + long duration = seconds * 1000L; + long time = now + duration; + + return new Date(time); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Method.java b/simple/simple-http/src/main/java/org/simpleframework/http/Method.java new file mode 100644 index 0000000..5bb5027 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Method.java @@ -0,0 +1,70 @@ +/* + * Method.java May 2012 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +/** + * The <code>Method</code> interface contains the common HTTP methods + * that are sent with a request. This only contains those methods + * that have been defined within the RFC 2616 specification. These + * are defined here for convenience and informational purposes. + * + * @author Niall Gallagher + */ +public interface Method { + + /** + * For use with a proxy that can dynamically switch to being a tunnel. + */ + String CONNECT = "CONNECT"; + + /** + * Requests that the origin server delete the resource identified. + */ + String DELETE = "DELETE"; + + /** + * Retrieve whatever information is identified by the request. + */ + String GET = "GET"; + + /** + * Retrieve only the headers for the resource that is requested. + */ + String HEAD = "HEAD"; + + /** + * Represents a request for the communication options available. + */ + String OPTIONS = "OPTIONS"; + + /** + * Request that the origin server accept the entity in the request. + */ + String POST = "POST"; + + /** + * Requests that the entity be stored as the resource specified + */ + String PUT = "PUT"; + + /** + * Invoke a remote application layer loop back of the request. + */ + String TRACE = "TRACE"; +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Part.java b/simple/simple-http/src/main/java/org/simpleframework/http/Part.java new file mode 100644 index 0000000..75660ea --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Part.java @@ -0,0 +1,107 @@ +/* + * Part.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import java.io.IOException; +import java.io.InputStream; + +/** + * The <code>Part</code> object is used to represent a part within + * a request message. Typically a part represents either a text + * parameter or a file, with associated headers. The contents of + * the part can be acquire as an <code>InputStream</code> or as a + * string encoded in the default HTTP encoding ISO-8859-1 or in + * the encoding specified with the Content-Type header. + * + * @author Niall Gallagher + */ +public interface Part { + + /** + * This method is used to determine the type of a part. Typically + * a part is either a text parameter or a file. If this is true + * then the content represented by the associated part is a file. + * + * @return this returns true if the associated part is a file + */ + boolean isFile(); + + /** + * This method is used to acquire the name of the part. Typically + * this is used when the part represents a text parameter rather + * than a file. However, this can also be used with a file part. + * + * @return this returns the name of the associated part + */ + String getName(); + + /** + * This method is used to acquire the file name of the part. This + * is used when the part represents a text parameter rather than + * a file. However, this can also be used with a file part. + * + * @return this returns the file name of the associated part + */ + String getFileName(); + + /** + * This is used to acquire the header value for the specified + * header name. Providing the header values through this method + * ensures any special processing for a know content type can be + * handled by an application. + * + * @param name the name of the header to get the value for + * + * @return value of the header mapped to the specified name + */ + String getHeader(String name); + + /** + * This is used to acquire the content of the part as a string. + * The encoding of the string is taken from the content type. + * If no content type is sent the content is decoded in the + * standard default of ISO-8859-1. + * + * @return this returns a string representing the content + * + * @throws IOException thrown if the content can not be created + */ + String getContent() throws IOException; + + /** + * This is used to acquire an <code>InputStream</code> for the + * part. Acquiring the stream allows the content of the part to + * be consumed by reading the stream. Each invocation of this + * method will produce a new stream starting from the first byte. + * + * @return this returns the stream for this part object + * + * @throws IOException thrown if the stream can not be created + */ + InputStream getInputStream() throws IOException; + + /** + * This is used to acquire the content type for this part. This + * is typically the type of content for a file part, as provided + * by a MIME type from the HTTP "Content-Type" header. + * + * @return this returns the content type for the part object + */ + ContentType getContentType(); +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Path.java b/simple/simple-http/src/main/java/org/simpleframework/http/Path.java new file mode 100644 index 0000000..fb07ef0 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Path.java @@ -0,0 +1,166 @@ +/* + * Path.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +/** + * The <code>Path</code> represents the path part of a URI. This provides + * the various components of the URI path to the user. The normalization + * of the path is the conversion of the path given into it's actual path by + * removing the references to the parent directories and to the current dir. + * <p> + * If the path that this represents is <code>/usr/bin/../etc/./README</code> + * then the actual path, normalized, is <code>/usr/etc/README</code>. Once + * the path has been normalized it is possible to acquire the segments as + * an array of strings, which allows simple manipulation of the path. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.parse.PathParser + */ +public interface Path { + + /** + * This will return the extension that the file name contains. + * For example a file name <code>file.en_US.extension</code> + * will produce an extension of <code>extension</code>. This + * will return null if the path contains no file extension. + * + * @return this will return the extension this path contains + */ + String getExtension(); + + /** + * This will return the full name of the file without the path. + * As regargs the definition of the path in RFC 2396 the name + * would be considered the last path segment. So if the path + * was <code>/usr/README</code> the name is <code>README</code>. + * Also for directorys the name of the directory in the last + * path segment is returned. This returns the name without any + * of the path parameters. As RFC 2396 defines the path to have + * path parameters after the path segments. + * + * @return this will return the name of the file in the path + */ + String getName(); + + /** + * This will return the normalized path. The normalized path is + * the path without any references to its parent or itself. So + * if the path to be parsed is <code>/usr/../etc/./</code> the + * path is <code>/etc/</code>. If the path that this represents + * is a path with an immediate back reference then this will + * return null. This is the path with all its information even + * the parameter information if it was defined in the path. + * + * @return this returns the normalize path without + * <code>../</code> or <code>./</code> + */ + String getPath(); + + /** + * This will return the normalized path from the specified path + * segment. This allows various path parts to be acquired in an + * efficient means what does not require copy operations of the + * use of <code>substring</code> invocations. Of particular + * interest is the extraction of context based paths. This is + * the path with all its information even the parameter + * information if it was defined in the path. + * + * @param from this is the segment offset to get the path for + * + * @return this returns the normalize path without + * <code>../</code> or <code>./</code> + */ + String getPath(int from); + + /** + * This will return the normalized path from the specified path + * segment. This allows various path parts to be acquired in an + * efficient means what does not require copy operations of the + * use of <code>substring</code> invocations. Of particular + * interest is the extraction of context based paths. This is + * the path with all its information even the parameter + * information if it was defined in the path. + * + * @param from this is the segment offset to get the path for + * @param count this is the number of path segments to include + * + * @return this returns the normalize path without + * <code>../</code> or <code>./</code> + */ + String getPath(int from, int count); + + /** + * This method is used to break the path into individual parts + * called segments, see RFC 2396. This can be used as an easy + * way to compare paths and to examine the directory tree that + * the path points to. For example, if an path was broken from + * the string <code>/usr/bin/../etc</code> then the segments + * returned would be <code>usr</code> and <code>etc</code> as + * the path is normalized before the segments are extracted. + * + * @return return all the path segments within the directory + */ + String[] getSegments(); + + /** + * This will return the highest directory that exists within + * the path. This is used to that files within the same path + * can be acquired. An example of that this would do given + * the path <code>/pub/./bin/README</code> would be to return + * the highest directory path <code>/pub/bin/</code>. The "/" + * character will allways be the last character in the path. + * + * @return this method will return the highest directory + */ + String getDirectory(); + + /** + * This will return the path as it is relative to the issued + * path. This in effect will chop the start of this path if + * it's start matches the highest directory of the given path + * as of <code>getDirectory</code>. This is useful if paths + * that are relative to a specific location are required. To + * illustrate what this method will do the following example + * is provided. If this object represented the path string + * <code>/usr/share/rfc/rfc2396.txt</code> and the issued + * path was <code>/usr/share/text.txt</code> then this will + * return the path string <code>/rfc/rfc2396.txt</code>. + * + * @param path the path prefix to acquire a relative path + * + * @return returns a path relative to the one it is given + * otherwize this method will return null + */ + String getRelative(String path); + + /** + * This will return the normalized path. The normalized path is + * the path without any references to its parent or itself. So + * if the path to be parsed is <code>/usr/../etc/./</code> the + * path is <code>/etc/</code>. If the path that this represents + * is a path with an immediate back reference then this will + * return null. This is the path with all its information even + * the parameter information if it was defined in the path. + * + * @return this returns the normalize path without + * <code>../</code> or <code>./</code> + */ + String toString(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Principal.java b/simple/simple-http/src/main/java/org/simpleframework/http/Principal.java new file mode 100644 index 0000000..361e4c1 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Principal.java @@ -0,0 +1,48 @@ +/* + * Principal.java November 2002 + * + * Copyright (C) 2002, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +/** + * The <code>Principal</code> interface is used to describe a + * user that has a name and password. This should not be + * confused with the <code>java.security.Principal</code> + * interface which does not provide <code>getPassword</code>. + * + * @author Niall Gallagher + */ +public interface Principal { + + /** + * The <code>getPassword</code> method is used to retrieve + * the password of the principal. This is the password + * tag in the RFC 2616 Authorization credentials expression. + * + * @return this returns the password for this principal + */ + String getPassword(); + + /** + * The <code>getName</code> method is used to retreive + * the name of the principal. This is the name tag in + * the RFC 2616 Authorization credentials expression. + * + * @return this returns the name of this principal + */ + String getName(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Protocol.java b/simple/simple-http/src/main/java/org/simpleframework/http/Protocol.java new file mode 100644 index 0000000..295b6c6 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Protocol.java @@ -0,0 +1,370 @@ +/* + * Protocol.java May 2012 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +/** + * This represents the HTTP header names defined in RFC 2616. It can be + * used to set and get headers safely from the <code>Request</code> and + * <code>Response</code> objects. This is used internally by the HTTP + * server to parse the incoming requests and also to submit response + * values for each conversation. + * <p> + * In addition to the header names this also contains some common + * HTTP header value tokens. These are provided for convenience and + * can be used to ensure that response values comply with RFC 2616. + * + * @author Niall Gallagher + */ +public interface Protocol { + + /** + * Specifies media types which are acceptable for the response. + */ + String ACCEPT = "Accept"; + + /** + * Indicates what character sets are acceptable for the response. + */ + String ACCEPT_CHARSET = "Accept-Charset"; + + /** + * Restricts the content codings that are acceptable in the response. + */ + String ACCEPT_ENCODING = "Accept-Encoding"; + + /** + * Restricts the set of languages that are preferred as a response. + */ + String ACCEPT_LANGUAGE = "Accept-Language"; + + /** + * Indicates a servers acceptance of range requests for a resource. + */ + String ACCEPT_RANGES = "Accept-Ranges"; + + /** + * Estimates the amount of time since the response was generated. + */ + String AGE = "Age"; + + /** + * Lists the set of methods supported by the resource identified. + */ + String ALLOW = "Allow"; + + /** + * Sent by a client that wishes to authenticate itself with a server. + */ + String AUTHORIZATION = "Authorization"; + + /** + * Specifies directives that must be obeyed by all caching mechanisms. + */ + String CACHE_CONTROL = "Cache-Control"; + + /** + * Specifies options that are desired for that particular connection. + */ + String CONNECTION = "Connection"; + + /** + * Specifies a tag indicating of its desired presentation semantics. + */ + String CONTENT_DISPOSITION = "Content-Disposition"; + + /** + * Indicates additional content codings have been applied to the body. + */ + String CONTENT_ENCODING = "Content-Encoding"; + + /** + * Describes the languages of the intended audience for the body. + */ + String CONTENT_LANGUAGE = "Content-Language"; + + /** + * Indicates the size of the entity body in decimal number of octets. + */ + String CONTENT_LENGTH = "Content-Length"; + + /** + * Used to supply the resource location for the entity enclosed. + */ + String CONTENT_LOCATION = "Content-Location"; + + /** + * An MD5 digest of the body for the purpose of checking integrity. + */ + String CONTENT_MD5 = "Content-MD5"; + + /** + * Specifies where in the full body a partial body should be applied. + */ + String CONTENT_RANGE = "Content-Range"; + + /** + * Indicates the media type of the body sent to the recipient. + */ + String CONTENT_TYPE = "Content-Type"; + + /** + * Represents a cookie that contains some information from the client. + */ + String COOKIE = "Cookie"; + + /** + * Represents the date and time at which the message was originated. + */ + String DATE = "Date"; + + /** + * Provides the value of the entity tag for the requested variant. + */ + String ETAG = "ETag"; + + /** + * Indicate that particular server behaviors are required by the client. + */ + String EXPECT = "Expect"; + + /** + * Gives the time after which the response is considered stale. + */ + String EXPIRES = "Expires"; + + /** + * Address for the human user who controls the requesting user agent. + */ + String FROM = "From"; + + /** + * Specifies the host and port number of the resource being requested. + */ + String HOST = "Host"; + + /** + * Specifies the entity tag for a request to make it conditional. + */ + String IF_MATCH = "If-Match"; + + /** + * If variant has not been modified since the time specified. + */ + String IF_MODIFIED_SINCE = "If-Modified-Since"; + + /** + * Verify that none of those entities is current by including a list. + */ + String IF_NONE_MATCH = "If-None-Match"; + + /** + * If the entity is unchanged send me the part that I am missing. + */ + String IF_RANGE = "If-Range"; + + /** + * If the requested resource has not been modified since this time. + */ + String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + + /** + * Indicates the date and time at which the variant was last modified. + */ + String LAST_MODIFIED = "Last-Modified"; + + /** + * Used to redirect the recipient to a location other than the URI. + */ + String LOCATION = "Location"; + + /** + * Limit the number of proxies or gateways that can forward the request. + */ + String MAX_FORWARDS = "Max-Forwards"; + + /** + * Include implementation specific directives that might apply. + */ + String PRAGMA = "Pragma"; + + /** + * Challenge indicating the authentication applicable to the proxy. + */ + String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + + /** + * Allows client identification for a proxy requiring authentication. + */ + String PROXY_AUTHORIZATION = "Proxy-Authorization"; + + /** + * Specifies a range of bytes within a resource to be sent by a server. + */ + String RANGE = "Range"; + + /** + * Allows the client to specify the source address to the server. + */ + String REFERER = "Referer"; + + /** + * Response to indicate how long the service will be unavailable. + */ + String RETRY_AFTER = "Retry-After"; + + /** + * Represents the globally unique identifier sent by the client. + */ + String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; + + /** + * Represents the SHA-1 digest of the clients globally unique identifier. + */ + String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept"; + + /** + * Specifies the protocol that should be used by the connected parties. + */ + String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; + + /** + * Represents the version of the protocol that should be used. + */ + String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version"; + + /** + * Contains information about the software used by the origin server. + */ + String SERVER = "Server"; + + /** + * Represents some value from the server that the client should keep. + */ + String SET_COOKIE = "Set-Cookie"; + + /** + * Indicates what extension transfer codings it is willing to accept. + */ + String TE = "TE"; + + /** + * Indicates that these header fields is present in the trailer. + */ + String TRAILER = "Trailer"; + + /** + * Indicates the transformation has been applied to the message body. + */ + String TRANSFER_ENCODING = "Transfer-Encoding"; + + /** + * Specifies additional communication protocols the client supports. + */ + String UPGRADE = "Upgrade"; + + /** + * Contains information about the user agent originating the request. + */ + String USER_AGENT = "User-Agent"; + + /** + * Indicates the headers that can make a cached resource stale. + */ + String VARY = "Vary"; + + /** + * Used by gateways and proxies to indicate the intermediate protocols. + */ + String VIA = "Via"; + + /** + * Used to carry additional information about the status or body. + */ + String WARNING = "Warning"; + + /** + * Uses to challenge a client for authentication for a resource. + */ + String WWW_AUTHENTICATE = "WWW-Authenticate"; + + /** + * Represents a class of data representing an executable application. + */ + String APPLICATION = "application"; + + /** + * Represents the token used to identify a multipart boundary. + */ + String BOUNDARY = "boundary"; + + /** + * Represents the token used to identify the encoding of a message. + */ + String CHARSET = "charset"; + + /** + * Represents the name of a self delimiting transfer encoding. + */ + String CHUNKED = "chunked"; + + /** + * Specifies that the server will terminate the connection. + */ + String CLOSE = "close"; + + /** + * Represents a message type for an image such as a PNG or JPEG. + */ + String IMAGE = "image"; + + /** + * Specifies that the server wishes to keep the connection open. + */ + String KEEP_ALIVE = "keep-alive"; + + /** + * Represents a message type that contains multiple parts. + */ + String MULTIPART = "multipart"; + + /** + * Specifies that the message should not be cached by anything. + */ + String NO_CACHE = "no-cache"; + + /** + * Represents the default content type if none is specified. + */ + String OCTET_STREAM = "octet-stream"; + + /** + * Represents a message type containing human readable text. + */ + String TEXT = "text"; + + /** + * Represents a message type that contains HTML form posted data. + */ + String URL_ENCODED = "x-www-form-urlencoded"; + + /** + * This is the protocol token that is used when upgrading. + */ + String WEBSOCKET = "websocket"; +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Query.java b/simple/simple-http/src/main/java/org/simpleframework/http/Query.java new file mode 100644 index 0000000..5ab8afa --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Query.java @@ -0,0 +1,99 @@ +/* + * Query.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import java.util.List; +import java.util.Map; + +/** + * The <code>Query</code> object is used to represent HTTP query + * parameters. Parameters are acquired by name and can be either a + * string, float, int, or boolean value. This ensures that data can + * be conveniently extracted in the correct type. This stores the + * parameters in a map of key value pairs. Each parameter can be + * acquired using the name of the parameter, if the parameter is + * named twice then all values can be acquired. + * + * @author Niall Gallagher + */ +public interface Query extends Map<String, String> { + + /** + * This method is used to acquire a <code>List</code> for all of + * the parameter values associated with the specified name. Using + * this method allows the query to expose many values taken from + * the query or HTTP form posting. Typically the first value in + * the list is the value from the <code>get(String)</code> method + * as this is the primary value from the ordered list of values. + * + * @param name this is the name used to search for the value + * + * @return this is the list of values associated with the key + */ + List<String> getAll(Object name); + + /** + * This extracts an integer parameter for the named value. If the + * named parameter does not exist this will return a zero value. + * If however the parameter exists but is not in the format of a + * decimal integer value then this will throw an exception. + * + * @param name the name of the parameter value to retrieve + * + * @return this returns the named parameter value as an integer + */ + int getInteger(Object name); + + /** + * This extracts a float parameter for the named value. If the + * named parameter does not exist this will return a zero value. + * If however the parameter exists but is not in the format of a + * floating point number then this will throw an exception. + * + * @param name the name of the parameter value to retrieve + * + * @return this returns the named parameter value as a float + */ + float getFloat(Object name); + + /** + * This extracts a boolean parameter for the named value. If the + * named parameter does not exist this will return false otherwise + * the value is evaluated. If it is either <code>true</code> or + * <code>false</code> then those boolean values are returned. + * + * @param name the name of the parameter value to retrieve + * + * @return this returns the named parameter value as an float + */ + boolean getBoolean(Object name); + + /** + * This will return all parameters represented using the HTTP + * URL query format. The <code>x-www-form-urlencoded</code> + * format is used to encode the attributes, see RFC 2616. + * <p> + * This will also encode any special characters that appear + * within the name and value pairs as an escaped sequence. + * If there are no parameters an empty string is returned. + * + * @return returns an empty string if the is no parameters + */ + String toString(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Request.java b/simple/simple-http/src/main/java/org/simpleframework/http/Request.java new file mode 100644 index 0000000..2c83c28 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Request.java @@ -0,0 +1,210 @@ +/* + * Request.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.nio.channels.ReadableByteChannel; +import java.util.List; +import java.util.Map; + +import org.simpleframework.transport.Certificate; +import org.simpleframework.transport.Channel; + +/** + * The <code>Request</code> is used to provide an interface to the + * HTTP entity body and message header. This provides methods that + * allow the entity body to be acquired as a stream, string, or if + * the message is a multipart encoded body, then the individual + * parts of the request body can be acquired. + * <p> + * This can also maintain data during the request lifecycle as well + * as the session lifecycle. A <code>Session</code> is made available + * for convenience. It provides a means for the services to associate + * data with a given client session, which can be retrieved when + * there are subsequent requests sent to the server. + * <p> + * It is important to note that the entity body can be read multiple + * times from the request. Calling <code>getInputStream</code> will + * start reading from the first byte in the body regardless of the + * number of times it is called. This allows POST parameters as well + * as multipart bodies to be read from the stream if desired. + * + * @author Niall Gallagher + */ +public interface Request extends RequestHeader { + + /** + * This is used to determine if the request has been transferred + * over a secure connection. If the protocol is HTTPS and the + * content is delivered over SSL then the request is considered + * to be secure. Also the associated response will be secure. + * + * @return true if the request is transferred securely + */ + boolean isSecure(); + + /** + * This is a convenience method that is used to determine whether + * or not this message has the <code>Connection: close</code> + * header. If the close token is present then this stream is not + * a keep-alive connection. If this has no <code>Connection</code> + * header then the keep-alive status is determined by the HTTP + * version, that is, HTTP/1.1 is keep-alive by default, HTTP/1.0 + * is not keep-alive by default. + * + * @return returns true if this has a keep-alive stream + */ + boolean isKeepAlive(); + + /** + * This is the time in milliseconds when the request was first + * read from the underlying socket. The time represented here + * represents the time collection of this request began. This + * does not necessarily represent the time the bytes arrived as + * as some data may have been buffered before it was parsed. + * + * @return this represents the time the request arrived at + */ + long getRequestTime(); + + /** + * This provides the underlying channel for the request. It + * contains the TCP socket channel and various other low level + * components. Typically this will only ever be needed when + * there is a need to switch protocols. + * + * @return the underlying channel for this request + */ + Channel getChannel(); + + /** + * This is used to acquire the SSL certificate used when the + * server is using a HTTPS connection. For plain text connections + * or connections that use a security mechanism other than SSL + * this will be null. This is only available when the connection + * makes specific use of an SSL engine to secure the connection. + * + * @return this returns the associated SSL certificate if any + */ + Certificate getClientCertificate(); + + /** + * This is used to acquire the remote client address. This can + * be used to acquire both the port and the I.P address for the + * client. It allows the connected clients to be logged and if + * require it can be used to perform course grained security. + * + * @return this returns the client address for this request + */ + InetSocketAddress getClientAddress(); + + /** + * This can be used to retrieve the response attributes. These can + * be used to keep state with the response when it is passed to + * other systems for processing. Attributes act as a convenient + * model for storing objects associated with the response. This + * also inherits attributes associated with the client connection. + * + * @return the attributes of that have been set on the request + */ + Map getAttributes(); + + /** + * This is used as a shortcut for acquiring attributes for the + * response. This avoids acquiring the attribute <code>Map</code> + * in order to retrieve the attribute directly from that object. + * The attributes contain data specific to the response. + * + * @param key this is the key of the attribute to acquire + * + * @return this returns the attribute for the specified name + */ + Object getAttribute(Object key); + + /** + * This is used to provide quick access to the parameters. This + * avoids having to acquire the request <code>Form</code> object. + * This basically acquires the parameters object and invokes + * the <code>getParameters</code> method with the given name. + * + * @param name this is the name of the parameter value + */ + String getParameter(String name); + + /** + * This method is used to acquire a <code>Part</code> from the + * HTTP request using a known name for the part. This is typically + * used when there is a file upload with a multipart POST request. + * All parts that are not files can be acquired as string values + * from the attachment object. + * + * @param name this is the name of the part object to acquire + * + * @return the named part or null if the part does not exist + */ + Part getPart(String name); + + /** + * This method is used to get all <code>Part</code> objects that + * are associated with the request. Each attachment contains the + * body and headers associated with it. If the request is not a + * multipart POST request then this will return an empty list. + * + * @return the list of parts associated with this request + */ + List<Part> getParts(); + + /** + * This is used to get the content body. This will essentially get + * the content from the body and present it as a single string. + * The encoding of the string is determined from the content type + * charset value. If the charset is not supported this will throw + * an exception. Typically only text values should be extracted + * using this method if there is a need to parse that content. + * + * @return this returns the message bytes as an encoded string + */ + String getContent() throws IOException; + + /** + * This is used to read the content body. The specifics of the data + * that is read from this <code>InputStream</code> can be determined + * by the <code>getContentLength</code> method. If the data sent by + * the client is chunked then it is decoded, see RFC 2616 section + * 3.6. Also multipart data is available as <code>Part</code> objects + * however the raw content of the multipart body is still available. + * + * @return this returns an input stream containing the message body + */ + InputStream getInputStream() throws IOException; + + /** + * This is used to read the content body. The specifics of the data + * that is read from this <code>ReadableByteChannel</code> can be + * determined by the <code>getContentLength</code> method. If the + * data sent by the client is chunked then it is decoded, see RFC + * 2616 section 3.6. This stream will never provide empty reads as + * the content is internally buffered, so this can do a full read. + * + * @return this returns the byte channel used to read the content + */ + ReadableByteChannel getByteChannel() throws IOException; +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/RequestHeader.java b/simple/simple-http/src/main/java/org/simpleframework/http/RequestHeader.java new file mode 100644 index 0000000..d1ca7d0 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/RequestHeader.java @@ -0,0 +1,201 @@ +/* + * RequestHeader.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import java.util.List; +import java.util.Locale; + +/** + * This is a <code>Header</code> object that is used to represent a + * basic form for the HTTP request message. This is used to extract + * values such as the request line and header values from the request + * message. Access to header values is done case insensitively. + * <p> + * As well as providing the header values and request line values + * this will also provide convenience methods which enable the user + * to determine the length of the body this message header prefixes. + * + * @author Niall Gallagher + */ +public interface RequestHeader extends RequestLine { + + /** + * This method is used to get a <code>List</code> of the names + * for the headers. This will provide the original names for the + * HTTP headers for the message. Modifications to the provided + * list will not affect the header, the list is a simple copy. + * + * @return this returns a list of the names within the header + */ + List<String> getNames(); + + /** + * This can be used to get the integer of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + int getInteger(String name); + + /** + * This can be used to get the date of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + long getDate(String name); + + /** + * This is used to acquire a cookie using the name of that cookie. + * If the cookie exists within the HTTP header then it is returned + * as a <code>Cookie</code> object. Otherwise this method will + * return null. Each cookie object will contain the name, value + * and path of the cookie as well as the optional domain part. + * + * @param name this is the name of the cookie object to acquire + * + * @return this returns a cookie object from the header or null + */ + Cookie getCookie(String name); + + /** + * This is used to acquire all cookies that were sent in the header. + * If any cookies exists within the HTTP header they are returned + * as <code>Cookie</code> objects. Otherwise this method will an + * empty list. Each cookie object will contain the name, value and + * path of the cookie as well as the optional domain part. + * + * @return this returns all cookie objects from the HTTP header + */ + List<Cookie> getCookies(); + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma separated list of + * values the value returned is the first value in that list. + * This returns null if theres no HTTP message header. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + String getValue(String name); + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma separated list of + * values the value returned is the first value in that list. + * This returns null if theres no HTTP message header. + * + * @param name the HTTP message header to get the value from + * @param index if there are multiple values this selects one + * + * @return this returns the value that the HTTP message header + */ + String getValue(String name, int index); + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benefits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearance. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has highest preference. + * + * @param name the name of the headers that are to be retrieved + * + * @return ordered array of tokens extracted from the header(s) + */ + List<String> getValues(String name); + + /** + * This is used to acquire the locales from the request header. The + * locales are provided in the <code>Accept-Language</code> header. + * This provides an indication as to the languages that the client + * accepts. It provides the locales in preference order. + * + * @return this returns the locales preferred by the client + */ + List<Locale> getLocales(); + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Type</code> header, if there is then + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content type value if it exists + */ + ContentType getContentType(); + + /** + * This is a convenience method that can be used to determine + * the length of the message body. This will determine if there + * is a <code>Content-Length</code> header, if it does then the + * length can be determined, if not then this returns -1. + * + * @return the content length, or -1 if it cannot be determined + */ + long getContentLength(); + + /** + * This method returns a <code>CharSequence</code> holding the header + * consumed for the request. A character sequence is returned as it + * can provide a much more efficient means of representing the header + * data by just wrapping the consumed byte array. + * + * @return this returns the characters consumed for the header + */ + CharSequence getHeader(); + + /** + * This method returns a string representing the header that was + * consumed for this request. For performance reasons it is better + * to acquire the character sequence representing the header as it + * does not require the allocation on new memory. + * + * @return this returns a string representation of this request + */ + String toString(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/RequestLine.java b/simple/simple-http/src/main/java/org/simpleframework/http/RequestLine.java new file mode 100644 index 0000000..b5b1abc --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/RequestLine.java @@ -0,0 +1,98 @@ +/* + * RequestLine.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +/** + * The <code>RequestLine</code> is used to represent a HTTP request + * line. The methods provided for this can be used to provide easy + * access to the components of a HTTP request line. For the syntax + * of a HTTP request line see RFC 2616. + * + * @author Niall Gallagher + */ +public interface RequestLine { + + /** + * This can be used to get the HTTP method for this request. The + * HTTP specification RFC 2616 specifies the HTTP request methods + * in section 9, Method Definitions. Typically this will be a + * GET, POST or a HEAD method, although any string is possible. + * + * @return the request method for this request message + */ + String getMethod(); + + /** + * This can be used to get the URI specified for this HTTP + * request. This corresponds to the /index part of a + * http://www.domain.com/index URL but may contain the full + * URL. This is a read only value for the request. + * + * @return the URI that this HTTP request is targeting + */ + String getTarget(); + + /** + * This is used to acquire the address from the request line. + * An address is the full URI including the scheme, domain, port + * and the query parts. This allows various parameters to be + * acquired without having to parse the raw request target URI. + * + * @return this returns the address of the request line + */ + Address getAddress(); + + /** + * This is used to acquire the path as extracted from the HTTP + * request URI. The <code>Path</code> object that is provided by + * this method is immutable, it represents the normalized path + * only part from the request uniform resource identifier. + * + * @return this returns the normalized path for the request + */ + Path getPath(); + + /** + * This method is used to acquire the query part from the + * HTTP request URI target. This will return only the values + * that have been extracted from the request URI target. + * + * @return the query associated with the HTTP target URI + */ + Query getQuery(); + + /** + * This can be used to get the major number from a HTTP version. + * The major version corresponds to the major type that is the 1 + * of a HTTP/1.0 version string. + * + * @return the major version number for the request message + */ + int getMajor(); + + /** + * This can be used to get the major number from a HTTP version. + * The major version corresponds to the major type that is the 0 + * of a HTTP/1.0 version string. This is used to determine if + * the request message has keep alive semantics. + * + * @return the major version number for the request message + */ + int getMinor(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/RequestWrapper.java b/simple/simple-http/src/main/java/org/simpleframework/http/RequestWrapper.java new file mode 100644 index 0000000..be81f5e --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/RequestWrapper.java @@ -0,0 +1,520 @@ +/* + * RequestWrapper.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.nio.channels.ReadableByteChannel; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.simpleframework.transport.Certificate; +import org.simpleframework.transport.Channel; + +/** + * The <code>RequestWrapper</code> object is used so that the original + * <code>Request</code> object can be wrapped in a filtering proxy + * object. This allows a <code>Container</code> that interacts with + * a modified request object. To add functionality to the request it + * can be wrapped in a subclass of this and the overridden methods + * can provide modified functionality to the standard request. + * + * @author Niall Gallagher + */ +public class RequestWrapper implements Request { + + /** + * This is the request instance that is being wrapped. + */ + protected Request request; + + /** + * Constructor for <code>RequestWrapper</code> object. This allows + * the original <code>Request</code> object to be wrapped so that + * adjustments to the behaviour of a request object handed to the + * container can be provided by a subclass implementation. + * + * @param request the request object that is being wrapped + */ + public RequestWrapper(Request request){ + this.request = request; + } + + /** + * This can be used to get the major number from a HTTP version. + * The major version corresponds to the major type that is the 1 + * of a HTTP/1.0 version string. + * + * @return the major version number for the request message + */ + public int getMajor() { + return request.getMajor(); + } + + /** + * This can be used to get the major number from a HTTP version. + * The major version corresponds to the major type that is the 0 + * of a HTTP/1.0 version string. This is used to determine if + * the request message has keep alive semantics. + * + * @return the major version number for the request message + */ + public int getMinor() { + return request.getMinor(); + } + + /** + * This can be used to get the HTTP method for this request. The + * HTTP specification RFC 2616 specifies the HTTP request methods + * in section 9, Method Definitions. Typically this will be a + * GET, POST or a HEAD method, although any string is possible. + * + * @return the request method for this request message + */ + public String getMethod() { + return request.getMethod(); + } + + /** + * This can be used to get the URI specified for this HTTP request. + * This corresponds to the either the full HTTP URI or the path + * part of the URI depending on how the client sends the request. + * + * @return the URI address that this HTTP request is targeting + */ + public String getTarget() { + return request.getTarget(); + } + + /** + * This is used to acquire the address from the request line. + * An address is the full URI including the scheme, domain, port + * and the query parts. This allows various parameters to be + * acquired without having to parse the raw request target URI. + * + * @return this returns the address of the request line + */ + public Address getAddress() { + return request.getAddress(); + } + + /** + * This is used to acquire the path as extracted from the HTTP + * request URI. The <code>Path</code> object that is provided by + * this method is immutable, it represents the normalized path + * only part from the request uniform resource identifier. + * + * @return this returns the normalized path for the request + */ + public Path getPath() { + return request.getPath(); + } + + /** + * This method is used to acquire the query part from the HTTP + * request URI target and a form post if it exists. Both the + * query and the form post are merge together in a single query. + * + * @return the query associated with the HTTP target URI + */ + public Query getQuery() { + return request.getQuery(); + } + + /** + * This method is used to get a <code>List</code> of the names + * for the headers. This will provide the original names for the + * HTTP headers for the message. Modifications to the provided + * list will not affect the header, the list is a simple copy. + * + * @return this returns a list of the names within the header + */ + public List<String> getNames() { + return request.getNames(); + } + + /** + * This can be used to get the integer of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + public int getInteger(String name) { + return request.getInteger(name); + } + + /** + * This can be used to get the date of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + public long getDate(String name) { + return request.getDate(name); + } + + /** + * This is used to acquire a cookie usiing the name of that cookie. + * If the cookie exists within the HTTP header then it is returned + * as a <code>Cookie</code> object. Otherwise this method will + * return null. Each cookie object will contain the name, value + * and path of the cookie as well as the optional domain part. + * + * @param name this is the name of the cookie object to acquire + * + * @return this returns a cookie object from the header or null + */ + public Cookie getCookie(String name) { + return request.getCookie(name); + } + + /** + * This is used to acquire all cookies that were sent in the header. + * If any cookies exists within the HTTP header they are returned + * as <code>Cookie</code> objects. Otherwise this method will an + * empty list. Each cookie object will contain the name, value and + * path of the cookie as well as the optional domain part. + * + * @return this returns all cookie objects from the HTTP header + */ + public List<Cookie> getCookies() { + return request.getCookies(); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma seperated list of + * values the value returned is the first value in that list. + * This returns null if theres no HTTP message header. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name) { + return request.getValue(name); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma separated list of + * values the value returned is the first value in that list. + * This returns null if theres no HTTP message header. + * + * @param name the HTTP message header to get the value from + * @param index if there are multiple values this selects one + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name, int index) { + return request.getValue(name, index); + } + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benifits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearence. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has higest preference. + * + * @param name the name of the headers that are to be retrieved + * + * @return ordered array of tokens extracted from the header(s) + */ + public List<String> getValues(String name) { + return request.getValues(name); + } + + /** + * This is used to acquire the locales from the request header. The + * locales are provided in the <code>Accept-Language</code> header. + * This provides an indication as to the languages that the client + * accepts. It provides the locales in preference order. + * + * @return this returns the locales preferred by the client + */ + public List<Locale> getLocales() { + return request.getLocales(); + } + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Type</code> header, if there is then + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content type value if it exists + */ + public ContentType getContentType() { + return request.getContentType(); + } + + /** + * This is a convenience method that can be used to determine + * the length of the message body. This will determine if there + * is a <code>Content-Length</code> header, if it does then the + * length can be determined, if not then this returns -1. + * + * @return the content length, or -1 if it cannot be determined + */ + public long getContentLength() { + return request.getContentLength(); + } + + /** + * This is used to determine if the request has been transferred + * over a secure connection. If the protocol is HTTPS and the + * content is delivered over SSL then the request is considered + * to be secure. Also the associated response will be secure. + * + * @return true if the request is transferred securely + */ + public boolean isSecure() { + return request.isSecure(); + } + + /** + * This is a convenience method that is used to determine whether + * or not this message has the <code>Connection: close</code> + * header. If the close token is present then this stream is not + * a keep-alive connection. If this has no <code>Connection</code> + * header then the keep-alive status is determined by the HTTP + * version, that is, HTTP/1.1 is keep-alive by default, HTTP/1.0 + * is not keep-alive by default. + * + * @return returns true if this has a keep-alive stream + */ + public boolean isKeepAlive() { + return request.isKeepAlive(); + } + + /** + * This is the time in milliseconds when the request was first + * read from the underlying socket. The time represented here + * represents the time collection of this request began. This + * does not necessarily represent the time the bytes arrived as + * as some data may have been buffered before it was parsed. + * + * @return this represents the time the request arrived at + */ + public long getRequestTime() { + return request.getRequestTime(); + } + + /** + * This provides the underlying channel for the request. It + * contains the TCP socket channel and various other low level + * components. Typically this will only ever be needed when + * there is a need to switch protocols. + * + * @return the underlying channel for this request + */ + public Channel getChannel() { + return request.getChannel(); + } + + /** + * This is used to acquire the SSL certificate used when the + * server is using a HTTPS connection. For plain text connections + * or connections that use a security mechanism other than SSL + * this will be null. This is only available when the connection + * makes specific use of an SSL engine to secure the connection. + * + * @return this returns the associated SSL certificate if any + */ + public Certificate getClientCertificate() { + return request.getClientCertificate(); + } + + /** + * This can be used to retrieve the response attributes. These can + * be used to keep state with the response when it is passed to + * other systems for processing. Attributes act as a convenient + * model for storing objects associated with the response. This + * also inherits attributes associated with the client connection. + * + * @return the attributes that have been set on this response + */ + public Map getAttributes() { + return request.getAttributes(); + } + + /** + * This is used as a shortcut for acquiring attributes for the + * response. This avoids acquiring the attribute <code>Map</code> + * in order to retrieve the attribute directly from that object. + * The attributes contain data specific to the response. + * + * @param key this is the key of the attribute to acquire + * + * @return this returns the attribute for the specified name + */ + public Object getAttribute(Object key) { + return request.getAttribute(key); + } + + /** + * This is used to acquire the remote client address. This can + * be used to acquire both the port and the I.P address for the + * client. It allows the connected clients to be logged and if + * require it can be used to perform course grained security. + * + * @return this returns the client address for this request + */ + public InetSocketAddress getClientAddress() { + return request.getClientAddress(); + } + + /** + * This method returns a <code>CharSequence</code> holding the header + * consumed for the request. A character sequence is returned as it + * can provide a much more efficient means of representing the header + * data by just wrapping the consumed byte array. + * + * @return this returns the characters consumed for the header + */ + public CharSequence getHeader() { + return request.getHeader(); + } + + /** + * This is used to get the content body. This will essentially get + * the content from the body and present it as a single string. + * The encoding of the string is determined from the content type + * charset value. If the charset is not supported this will throw + * an exception. Typically only text values should be extracted + * using this method if there is a need to parse that content. + * + * @exception IOException signifies that there is an I/O problem + * + * @return the body content as an encoded string value + */ + public String getContent() throws IOException { + return request.getContent(); + } + + /** + * This is used to read the content body. The specifics of the data + * that is read from this <code>InputStream</code> can be determined + * by the <code>getContentLength</code> method. If the data sent by + * the client is chunked then it is decoded, see RFC 2616 section + * 3.6. Also multipart data is available as <code>Part</code> objects + * however the raw content of the multipart body is still available. + * + * @exception Exception signifies that there is an I/O problem + * + * @return returns the input stream containing the message body + */ + public InputStream getInputStream() throws IOException { + return request.getInputStream(); + } + + /** + * This is used to read the content body. The specifics of the data + * that is read from this <code>ReadableByteChannel</code> can be + * determined by the <code>getContentLength</code> method. If the + * data sent by the client is chunked then it is decoded, see RFC + * 2616 section 3.6. This stream will never provide empty reads as + * the content is internally buffered, so this can do a full read. + * + * @return this returns the byte channel used to read the content + */ + public ReadableByteChannel getByteChannel() throws IOException { + return request.getByteChannel(); + } + + /** + * This is used to provide quick access to the parameters. This + * avoids having to acquire the request <code>Form</code> object. + * This basically acquires the parameters object and invokes + * the <code>getParameters</code> method with the given name. + * + * @param name this is the name of the parameter value + */ + public String getParameter(String name) { + return request.getParameter(name); + } + + /** + * This method is used to acquire a <code>Part</code> from the + * HTTP request using a known name for the part. This is typically + * used when there is a file upload with a multipart POST request. + * All parts that are not files can be acquired as string values + * from the attachment object. + * + * @param name this is the name of the part object to acquire + * + * @return the named part or null if the part does not exist + */ + public Part getPart(String name) { + return request.getPart(name); + } + + /** + * This method is used to get all <code>Part</code> objects that + * are associated with the request. Each attachment contains the + * body and headers associated with it. If the request is not a + * multipart POST request then this will return an empty list. + * + * @return the list of parts associated with this request + */ + public List<Part> getParts() { + return request.getParts(); + } + + /** + * This method returns a string representing the header that was + * consumed for this request. For performance reasons it is better + * to acquire the character sequence representing the header as it + * does not require the allocation on new memory. + * + * @return this returns a string representation of this request + */ + public String toString() { + return request.toString(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Response.java b/simple/simple-http/src/main/java/org/simpleframework/http/Response.java new file mode 100644 index 0000000..e9e54da --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Response.java @@ -0,0 +1,262 @@ +/* + * Response.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.channels.WritableByteChannel; + +/** + * This is used to represent the HTTP response. This provides methods + * that can be used to set various characteristics of the response. + * An <code>OutputStream</code> can be acquired via this interface + * which can be used to write the response body. A buffer size can be + * specified when acquiring the output stream which allows data to + * be buffered until it over flows or is flushed explicitly. This + * buffering allows a partially written response body to be reset. + * <p> + * This should never allow the message body be sent if it should not + * be sent with the headers as of RFC 2616 rules for the presence of + * a message body. A message body must not be included with a HEAD + * request or with a 304 or a 204 response. A proper implementation + * of this will prevent a message body being sent if the response + * is to a HEAD request of if there is a 304 or 204 response code. + * <p> + * It is important to note that the <code>Response</code> controls + * the processing of the HTTP pipeline. The next HTTP request is + * not processed until the response has been sent. To ensure that + * the response is sent the <code>close</code> method of the response + * or the output stream should be used. This will notify the server + * to dispatch the next request in the pipeline for processing. + * + * @author Niall Gallagher + */ +public interface Response extends ResponseHeader { + + /** + * This should be used when the size of the message body is known. + * This ensures that Persistent HTTP (PHTTP) connections can be + * maintained for both HTTP/1.0 and HTTP/1.1 clients. If the length + * of the output is not known HTTP/1.0 clients will require a + * connection close, which reduces performance (see RFC 2616). + * <p> + * This removes any previous Content-Length headers from the message + * header. This will then set the appropriate Content-Length header + * with the correct length. If a the Connection header is set with the + * close token then the semantics of the connection are such that the + * server will close it once the output stream or request is closed. + * + * @param length this is the length of the HTTP message body + */ + void setContentLength(long length); + + /** + * This is used to set the content type for the response. Typically + * a response will contain a message body of some sort. This is used + * to conveniently set the type for that response. Setting the + * content type can also be done explicitly if desired. + * + * @param type this is the type that is to be set in the response + */ + void setContentType(String type); + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>OutputStream</code> will be determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * + * @return an output stream object with the specified semantics + */ + OutputStream getOutputStream() throws IOException; + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>OutputStream</code> will be determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * <p> + * This will ensure that there is buffering done so that the output + * can be reset using the <code>reset</code> method. This will + * enable the specified number of bytes to be written without + * committing the response. This specified size is the minimum size + * that the response buffer must be. + * + * @return an output stream object with the specified semantics + */ + OutputStream getOutputStream(int size) throws IOException; + + /** + * This method is provided for convenience so that the HTTP content + * can be written using the <code>print</code> methods provided by + * the <code>PrintStream</code>. This will basically wrap the + * <code>getOutputStream</code> with a buffer size of zero. + * <p> + * The retrieved <code>PrintStream</code> uses the charset used to + * describe the content, with the Content-Type header. This will + * check the charset parameter of the contents MIME type. So if + * the Content-Type was <code>text/plain; charset=UTF-8</code> the + * resulting <code>PrintStream</code> would encode the written data + * using the UTF-8 encoding scheme. Care must be taken to ensure + * that bytes written to the stream are correctly encoded. + * <p> + * Implementations of the <code>Response</code> must guarantee + * that this can be invoked repeatedly without effecting any issued + * <code>OutputStream</code> or <code>PrintStream</code> object. + * + * @return a print stream that provides convenience writing + */ + PrintStream getPrintStream() throws IOException; + + /** + * This method is provided for convenience so that the HTTP content + * can be written using the <code>print</code> methods provided by + * the <code>PrintStream</code>. This will basically wrap the + * <code>getOutputStream</code> with a specified buffer size. + * <p> + * The retrieved <code>PrintStream</code> uses the charset used to + * describe the content, with the Content-Type header. This will + * check the charset parameter of the contents MIME type. So if + * the Content-Type was <code>text/plain; charset=UTF-8</code> the + * resulting <code>PrintStream</code> would encode the written data + * using the UTF-8 encoding scheme. Care must be taken to ensure + * that bytes written to the stream are correctly encoded. + * <p> + * Implementations of the <code>Response</code> must guarantee + * that this can be invoked repeatedly without effecting any issued + * <code>OutputStream</code> or <code>PrintStream</code> object. + * + * @param size the minimum size that the response buffer must be + * + * @return a print stream that provides convenience writing + */ + PrintStream getPrintStream(int size) throws IOException; + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>WritableByteChannel</code> are determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * + * @return a writable byte channel used to write the message body + */ + WritableByteChannel getByteChannel() throws IOException; + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>WritableByteChannel</code> are determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * <p> + * This will ensure that there is buffering done so that the output + * can be reset using the <code>reset</code> method. This will + * enable the specified number of bytes to be written without + * committing the response. This specified size is the minimum size + * that the response buffer must be. + * + * @param size the minimum size that the response buffer must be + * + * @return a writable byte channel used to write the message body + */ + WritableByteChannel getByteChannel(int size) throws IOException; + + /** + * This represents the time at which the response has fully written. + * Because the response is delivered asynchronously to the client + * this response time does not represent the time to last byte. + * It simply represents the time at which the response has been + * fully generated and written to the output buffer or queue. This + * returns zero if the response has not finished. + * + * @return this is the time taken to complete the response + */ + long getResponseTime(); + + /** + * This is used to determine if the HTTP response message is a + * keep alive message or if the underlying socket was closed. Even + * if the client requests a connection keep alive and supports + * persistent connections, the response can still be closed by + * the server. This can be explicitly indicated by the presence + * of the <code>Connection</code> HTTP header, it can also be + * implicitly indicated by using version HTTP/1.0. + * + * @return this returns true if the connection was closed + */ + boolean isKeepAlive(); + + /** + * This can be used to determine whether the <code>Response</code> + * has been committed. This is true if the <code>Response</code> + * was committed, either due to an explicit invocation of the + * <code>commit</code> method or due to the writing of content. If + * the <code>Response</code> has committed the <code>reset</code> + * method will not work in resetting content already written. + * + * @return true if the response headers have been committed + */ + boolean isCommitted(); + + /** + * This is used to write the headers that where given to the + * <code>Response</code>. Any further attempts to give headers + * to the <code>Response</code> will be futile as only the headers + * that were given at the time of the first commit will be used + * in the message header. + * <p> + * This also performs some final checks on the headers submitted. + * This is done to determine the optimal performance of the + * output. If no specific Connection header has been specified + * this will set the connection so that HTTP/1.0 closes by default. + * + * @exception IOException thrown if there was a problem writing + */ + void commit() throws IOException; + + /** + * This can be used to determine whether the <code>Response</code> + * has been committed. This is true if the <code>Response</code> + * was committed, either due to an explicit invocation of the + * <code>commit</code> method or due to the writing of content. If + * the <code>Response</code> has committed the <code>reset</code> + * method will not work in resetting content already written. + * + * @throws IOException thrown if there is a problem resetting + */ + void reset() throws IOException; + + /** + * This is used to close the connection and commit the request. + * This provides the same semantics as closing the output stream + * and ensures that the HTTP response is committed. This will + * throw an exception if the response can not be committed. + * + * @throws IOException thrown if there is a problem writing + */ + void close() throws IOException; +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/ResponseHeader.java b/simple/simple-http/src/main/java/org/simpleframework/http/ResponseHeader.java new file mode 100644 index 0000000..5b36994 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/ResponseHeader.java @@ -0,0 +1,304 @@ +/* + * Response.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import java.util.List; + +/** + * The <code>ResponseHeader</code> object is used to manipulate the + * header information for a given response. Headers are stored and + * retrieved from this object in a case insensitive manner. This + * implements the <code>StatusLine</code> object, which exposes the + * protocol version and response status code. + * <p> + * All cookies set on the response header will be delivered as a + * Set-Cookie header in the response message. The Content-Length and + * Transfer-Encoding headers can be set to configure how the message + * body is delivered to the connected client. + * + * @author Niall Gallagher + */ +public interface ResponseHeader extends StatusLine { + + /** + * This is used to acquire the names of the of the headers that + * have been set in the response. This can be used to acquire all + * header values by name that have been set within the response. + * If no headers have been set this will return an empty list. + * + * @return a list of strings representing the set header names + */ + List<String> getNames(); + + /** + * This can be used to add a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + void addValue(String name, String value); + + /** + * This can be used to add a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getInteger</code> in combination with the get methods. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + void addInteger(String name, int value); + + /** + * This is used as a convenience method for adding a header that + * needs to be parsed into a HTTPdate string. This will convert + * the date given into a date string defined in RFC 2616 sec 3.3.1. + * + * @param name the name of the HTTP message header to be added + * @param date the value constructed as an RFC 1123 date string + */ + void addDate(String name, long date); + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + void setValue(String name, String value); + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + void setInteger(String name, int value); + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + void setLong(String name, long value); + + /** + * This is used as a convenience method for adding a header that + * needs to be parsed into a HTTP date string. This will convert + * the date given into a date string defined in RFC 2616 sec 3.3.1. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param date the value constructed as an RFC 1123 date string + */ + void setDate(String name, long date); + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the full string + * representing the named header value. If the named header does + * not exist then this will return a null value. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + String getValue(String name); + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the full string + * representing the named header value. If the named header does + * not exist then this will return a null value. + * + * @param name the HTTP message header to get the value from + * @param index used if there are multiple headers present + * + * @return this returns the value that the HTTP message header + */ + String getValue(String name, int index); + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the integer + * representing the named header value. If the named header does + * not exist then this will return a value of minus one, -1. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + int getInteger(String name); + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the long value + * representing the named header value. If the named header does + * not exist then this will return a value of minus one, -1. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + long getDate(String name); + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benefits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearance. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has highest preference. + * + * @param name the name of the headers that are to be retrieved + * + * @return ordered list of tokens extracted from the header(s) + */ + List<String> getValues(String name); + + /** + * The <code>setCookie</code> method is used to set a cookie value + * with the cookie name. This will add a cookie to the response + * stored under the name of the cookie, when this is committed it + * will be added as a Set-Cookie header to the resulting response. + * + * @param cookie this is the cookie to be added to the response + * + * @return returns the cookie that has been set in the response + */ + Cookie setCookie(Cookie cookie); + + /** + * The <code>setCookie</code> method is used to set a cookie value + * with the cookie name. This will add a cookie to the response + * stored under the name of the cookie, when this is committed it + * will be added as a Set-Cookie header to the resulting response. + * This is a convenience method that avoids cookie creation. + * + * @param name this is the cookie to be added to the response + * @param value this is the cookie value that is to be used + * + * @return returns the cookie that has been set in the response + */ + Cookie setCookie(String name, String value); + + /** + * This returns the <code>Cookie</code> object stored under the + * specified name. This is used to retrieve cookies that have been + * set with the <code>setCookie</code> methods. If the cookie does + * not exist under the specified name this will return null. + * + * @param name this is the name of the cookie to be retrieved + * + * @return returns the <code>Cookie</code> by the given name + */ + Cookie getCookie(String name); + + /** + * This returns all <code>Cookie</code> objects stored under the + * specified name. This is used to retrieve cookies that have been + * set with the <code>setCookie</code> methods. If there are no + * cookies then this will return an empty list. + * + * @return returns all the <code>Cookie</code> in the response + */ + List<Cookie> getCookies(); + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Type</code> header, if there is then + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content type value if it exists + */ + ContentType getContentType(); + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Transfer-Encoding</code> header, if there is + * then this will parse that header and return the first token in + * the comma separated list of values, which is the primary value. + * + * @return this returns the transfer encoding value if it exists + */ + String getTransferEncoding(); + + /** + * This is a convenience method that can be used to determine + * the length of the message body. This will determine if there + * is a <code>Content-Length</code> header, if it does then the + * length can be determined, if not then this returns -1. + * + * @return content length, or -1 if it cannot be determined + */ + long getContentLength(); + + /** + * This method returns a <code>CharSequence</code> holding the header + * created for the request. A character sequence is returned as it + * can provide a much more efficient means of representing the header + * data by just wrapping the the data generated. + * + * @return this returns the characters generated for the header + */ + CharSequence getHeader(); + + /** + * This method returns a string representing the header that was + * generated for this header. For performance reasons it is better + * to acquire the character sequence representing the header as it + * does not require the allocation on new memory. + * + * @return this returns a string representation of this response + */ + String toString(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/ResponseWrapper.java b/simple/simple-http/src/main/java/org/simpleframework/http/ResponseWrapper.java new file mode 100644 index 0000000..240384c --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/ResponseWrapper.java @@ -0,0 +1,747 @@ +/* + * ResponseWrapper.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.channels.WritableByteChannel; +import java.util.List; + +/** + * The <code>ResponseWrapper</code> object is used so that the original + * <code>Response</code> object can be wrapped in a filtering proxy + * object. This allows a container to interact with an implementation + * of this with overridden methods providing specific functionality. + * the <code>Response</code> object in a concurrent environment. + * <pre> + * + * public void handle(Request req, Response resp) { + * handler.handle(req, new ZipResponse(resp)); + * } + * + * </pre> + * The above is an example of how the <code>ResponseWrapper</code> can + * be used to provide extra functionality to a <code>Response</code> + * in a transparent manner. Such an implementation could apply a + * Content-Encoding header and compress the response for performance + * over a slow network. Filtering can be applied with the use of + * layered <code>Container</code> objects. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.core.Container + */ +public class ResponseWrapper implements Response { + + /** + * This is the response instance that is being wrapped. + */ + protected Response response; + + /** + * Constructor for <code>ResponseWrapper</code> object. This allows + * the original <code>Response</code> object to be wrapped so that + * adjustments to the behavior of a request object handed to the + * container can be provided by a subclass implementation. + * + * @param response the response object that is being wrapped + */ + public ResponseWrapper(Response response){ + this.response = response; + } + + /** + * This represents the status code of the HTTP response. + * The response code represents the type of message that is + * being sent to the client. For a description of the codes + * see RFC 2616 section 10, Status Code Definitions. + * + * @return the status code that this HTTP response has + */ + public int getCode() { + return response.getCode(); + } + + /** + * This method allows the status for the response to be + * changed. This MUST be reflected the the response content + * given to the client. For a description of the codes see + * RFC 2616 section 10, Status Code Definitions. + * + * @param code the new status code for the HTTP response + */ + public void setCode(int code) { + response.setCode(code); + } + + /** + * This can be used to retrieve the text of a HTTP status + * line. This is the text description for the status code. + * This should match the status code specified by the RFC. + * + * @return the message description of the response + */ + public String getDescription() { + return response.getDescription(); + } + + /** + * This is used to set the text of the HTTP status line. + * This should match the status code specified by the RFC. + * + * @param text the descriptive text message of the status + */ + public void setDescription(String text) { + response.setDescription(text); + } + + /** + * This is used to acquire the status from the response. + * The <code>Status</code> object returns represents the + * code that has been set on the response, it does not + * necessarily represent the description in the response. + * + * @return this is the response for this status line + */ + public Status getStatus() { + return response.getStatus(); + } + + /** + * This is used to set the status code and description + * for this response. Setting the code and description in + * this manner provides a much more convenient way to set + * the response status line details. + * + * @param status this is the status to set on the response + */ + public void setStatus(Status status) { + response.setStatus(status); + } + + /** + * This can be used to get the major number from a HTTP version. + * The major version corresponds to the major type that is the 1 + * of a HTTP/1.0 version string. + * + * @return the major version number for the request message + */ + public int getMajor() { + return response.getMajor(); + } + + /** + * This can be used to set the major number from a HTTP version. + * The major version corresponds to the major type that is the 1 + * of a HTTP/1.0 version string. + * + * @param major the major version number for the request message + */ + public void setMajor(int major) { + response.setMajor(major); + } + + /** + * This can be used to get the minor number from a HTTP version. + * The minor version corresponds to the major type that is the 0 + * of a HTTP/1.0 version string. This is used to determine if + * the request message has keep alive semantics. + * + * @return the minor version number for the request message + */ + public int getMinor() { + return response.getMinor(); + } + + /** + * This can be used to get the minor number from a HTTP version. + * The minor version corresponds to the major type that is the 0 + * of a HTTP/1.0 version string. This is used to determine if + * the request message has keep alive semantics. + * + * @param minor the minor version number for the request message + */ + public void setMinor(int minor) { + response.setMinor(minor); + } + + /** + * This represents the time at which the response has fully written. + * Because the response is delivered asynchronously to the client + * this response time does not represent the time to last byte. + * It simply represents the time at which the response has been + * fully generated and written to the output buffer or queue. This + * returns zero if the response has not finished. + * + * @return this is the time taken to complete the response + */ + public long getResponseTime() { + return response.getResponseTime(); + } + + /** + * This is used to acquire the names of the of the headers that + * have been set in the response. This can be used to acquire all + * header values by name that have been set within the response. + * If no headers have been set this will return an empty list. + * + * @return a list of strings representing the set header names + */ + public List<String> getNames() { + return response.getNames(); + } + + /** + * This can be used to add a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void addValue(String name, String value) { + response.addValue(name, value); + } + + /** + * This can be used to add a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getInteger</code> in combination with the get methods. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void addInteger(String name, int value) { + response.addInteger(name, value); + } + + /** + * This is used as a convenience method for adding a header that + * needs to be parsed into a HTTPdate string. This will convert + * the date given into a date string defined in RFC 2616 sec 3.3.1. + * + * @param name the name of the HTTP message header to be added + * @param date the value constructed as an RFC 1123 date string + */ + public void addDate(String name, long date) { + response.addDate(name, date); + } + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void setValue(String name, String value) { + response.setValue(name, value); + } + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void setInteger(String name, int value) { + response.setInteger(name, value); + } + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void setLong(String name, long value) { + response.setLong(name, value); + } + + /** + * This is used as a convenience method for adding a header that + * needs to be parsed into a HTTP date string. This will convert + * the date given into a date string defined in RFC 2616 sec 3.3.1. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param date the value constructed as an RFC 1123 date string + */ + public void setDate(String name, long date) { + response.setDate(name, date); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the full string + * representing the named header value. If the named header does + * not exist then this will return a null value. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name) { + return response.getValue(name); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the full string + * representing the named header value. If the named header does + * not exist then this will return a null value. + * + * @param name the HTTP message header to get the value from + * @param index used if there are multiple headers present + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name, int index) { + return response.getValue(name, index); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the integer + * representing the named header value. If the named header does + * not exist then this will return a value of minus one, -1. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public int getInteger(String name) { + return response.getInteger(name); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the long value + * representing the named header value. If the named header does + * not exist then this will return a value of minus one, -1. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public long getDate(String name) { + return response.getDate(name); + } + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benefits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearance. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has highest preference. + * + * @param name the name of the headers that are to be retrieved + * + * @return ordered list of tokens extracted from the header(s) + */ + public List<String> getValues(String name) { + return response.getValues(name); + } + + /** + * The <code>setCookie</code> method is used to set a cookie value + * with the cookie name. This will add a cookie to the response + * stored under the name of the cookie, when this is committed it + * will be added as a Set-Cookie header to the resulting response. + * + * @param cookie this is the cookie to be added to the response + * + * @return returns the cookie that has been set in the response + */ + public Cookie setCookie(Cookie cookie) { + return response.setCookie(cookie); + } + + /** + * The <code>setCookie</code> method is used to set a cookie value + * with the cookie name. This will add a cookie to the response + * stored under the name of the cookie, when this is committed it + * will be added as a Set-Cookie header to the resulting response. + * This is a convenience method that avoids cookie creation. + * + * @param name this is the cookie to be added to the response + * @param value this is the cookie value that is to be used + * + * @return returns the cookie that has been set in the response + */ + public Cookie setCookie(String name, String value) { + return response.setCookie(name, value); + } + + /** + * This returns the <code>Cookie</code> object stored under the + * specified name. This is used to retrieve cookies that have been + * set with the <code>setCookie</code> methods. If the cookie does + * not exist under the specified name this will return null. + * + * @param name this is the name of the cookie to be retrieved + * + * @return returns the cookie object send with the request + */ + public Cookie getCookie(String name) { + return response.getCookie(name); + } + + /** + * This returns all <code>Cookie</code> objects stored under the + * specified name. This is used to retrieve cookies that have been + * set with the <code>setCookie</code> methods. If there are no + * cookies then this will return an empty list. + * + * @return returns all the cookie objects for this response + */ + public List<Cookie> getCookies() { + return response.getCookies(); + } + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Type</code> header, if there is then + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content type value if it exists + */ + public ContentType getContentType() { + return response.getContentType(); + } + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Transfer-Encoding</code> header, if there is + * then this will parse that header and return the first token in + * the comma separated list of values, which is the primary value. + * + * @return this returns the transfer encoding value if it exists + */ + public String getTransferEncoding() { + return response.getTransferEncoding(); + } + + /** + * This is a convenience method that can be used to determine + * the length of the message body. This will determine if there + * is a <code>Content-Length</code> header, if it does then the + * length can be determined, if not then this returns -1. + * + * @return content length, or -1 if it cannot be determined + */ + public long getContentLength() { + return response.getContentLength(); + } + + /** + * This should be used when the size of the message body is known. For + * performance reasons this should be used so the length of the output + * is known. This ensures that Persistent HTTP (PHTTP) connections + * can be maintained for both HTTP/1.0 and HTTP/1.1 clients. If the + * length of the output is not known HTTP/1.0 clients will require a + * connection close, which reduces performance (see RFC 2616). + * <p> + * This removes any previous Content-Length headers from the message + * header. This will then set the appropriate Content-Length header with + * the correct length. If a the Connection header is set with the close + * token then the semantics of the connection are such that the server + * will close it once the <code>OutputStream.close</code> is used. + * + * @param length this is the length of the HTTP message body + */ + public void setContentLength(long length) { + response.setContentLength(length); + } + + /** + * This is used to set the content type for the response. Typically + * a response will contain a message body of some sort. This is used + * to conveniently set the type for that response. Setting the + * content type can also be done explicitly if desired. + * + * @param type this is the type that is to be set in the response + */ + public void setContentType(String type) { + response.setContentType(type); + } + + /** + * This method returns a <code>CharSequence</code> holding the header + * created for the request. A character sequence is returned as it + * can provide a much more efficient means of representing the header + * data by just wrapping the the data generated. + * + * @return this returns the characters generated for the header + */ + public CharSequence getHeader() { + return response.getHeader(); + } + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>OutputStream</code> will be determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * The <code>OutputStream</code> issued must be thread safe so that + * it can be used in a concurrent environment. + * + * @exception IOException this is thrown if there was an I/O error + * + * @return an output stream used to write the response body + */ + public OutputStream getOutputStream() throws IOException { + return response.getOutputStream(); + } + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>OutputStream</code> will be determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * The <code>OutputStream</code> issued must be thread safe so that + * it can be used in a concurrent environment. + * <p> + * This will ensure that there is buffering done so that the output + * can be reset using the <code>reset</code> method. This will + * enable the specified number of bytes to be written without + * committing the response. This specified size is the minimum size + * that the response buffer must be. + * + * @param size the minimum size that the response buffer must be + * + * @return an output stream used to write the response body + * + * @exception IOException this is thrown if there was an I/O error + */ + public OutputStream getOutputStream(int size) throws IOException { + return response.getOutputStream(size); + } + + /** + * This method is provided for convenience so that the HTTP content + * can be written using the <code>print</code> methods provided by + * the <code>PrintStream</code>. This will basically wrap the + * <code>getOutputStream</code> with a buffer size of zero. + * <p> + * The retrieved <code>PrintStream</code> uses the charset used to + * describe the content, with the Content-Type header. This will + * check the charset parameter of the contents MIME type. So if + * the Content-Type was <code>text/plain; charset=UTF-8</code> the + * resulting <code>PrintStream</code> would encode the written data + * using the UTF-8 encoding scheme. Care must be taken to ensure + * that bytes written to the stream are correctly encoded. + * <p> + * Implementations of the <code>Response</code> must guarantee + * that this can be invoked repeatedly without effecting any issued + * <code>OutputStream</code> or <code>PrintStream</code> object. + * + * @return a print stream used for writing the response body + * + * @exception IOException this is thrown if there was an I/O error + */ + public PrintStream getPrintStream() throws IOException { + return response.getPrintStream(); + } + + /** + * This method is provided for convenience so that the HTTP content + * can be written using the <code>print</code> methods provided by + * the <code>PrintStream</code>. This will basically wrap the + * <code>getOutputStream</code> with a specified buffer size. + * <p> + * The retrieved <code>PrintStream</code> uses the charset used to + * describe the content, with the Content-Type header. This will + * check the charset parameter of the contents MIME type. So if + * the Content-Type was <code>text/plain; charset=UTF-8</code> the + * resulting <code>PrintStream</code> would encode the written data + * using the UTF-8 encoding scheme. Care must be taken to ensure + * that bytes written to the stream are correctly encoded. + * <p> + * Implementations of the <code>Response</code> must guarantee + * that this can be invoked repeatedly without effecting any issued + * <code>OutputStream</code> or <code>PrintStream</code> object. + * + * @param size the minimum size that the response buffer must be + * + * @return a print stream used for writing the response body + * + * @exception IOException this is thrown if there was an I/O error + */ + public PrintStream getPrintStream(int size) throws IOException { + return response.getPrintStream(size); + } + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>WritableByteChannel</code> are determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * + * @return a writable byte channel used to write the message body + */ + public WritableByteChannel getByteChannel() throws IOException { + return response.getByteChannel(); + } + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>WritableByteChannel</code> are determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * <p> + * This will ensure that there is buffering done so that the output + * can be reset using the <code>reset</code> method. This will + * enable the specified number of bytes to be written without + * committing the response. This specified size is the minimum size + * that the response buffer must be. + * + * @param size the minimum size that the response buffer must be + * + * @return a writable byte channel used to write the message body + */ + public WritableByteChannel getByteChannel(int size) throws IOException { + return response.getByteChannel(size); + } + + /** + * This is used to determine if the HTTP response message is a + * keep alive message or if the underlying socket was closed. Even + * if the client requests a connection keep alive and supports + * persistent connections, the response can still be closed by + * the server. This can be explicitly indicated by the presence + * of the <code>Connection</code> HTTP header, it can also be + * implicitly indicated by using version HTTP/1.0. + * + * @return this returns true if the connection was closed + */ + public boolean isKeepAlive() { + return response.isKeepAlive(); + } + + /** + * This can be used to determine whether the <code>Response</code> + * has been committed. This is true if the <code>Response</code> + * was committed, either due to an explicit invocation of the + * <code>commit</code> method or due to the writing of content. If + * the <code>Response</code> has committed the <code>reset</code> + * method will not work in resetting content already written. + * + * @return true if the response has been fully committed + */ + public boolean isCommitted() { + return response.isCommitted(); + } + + /** + * This is used to write the headers that where given to the + * <code>Response</code>. Any further attempts to give headers + * to the <code>Response</code> will be futile as only the headers + * that were given at the time of the first commit will be used + * in the message header. + * <p> + * This also performs some final checks on the headers submitted. + * This is done to determine the optimal performance of the + * output. If no specific Connection header has been specified + * this will set the connection so that HTTP/1.0 closes by default. + * + * @exception IOException thrown if there was a problem writing + */ + public void commit() throws IOException { + response.commit(); + } + + /** + * This can be used to determine whether the <code>Response</code> + * has been committed. This is true if the <code>Response</code> + * was committed, either due to an explicit invocation of the + * <code>commit</code> method or due to the writing of content. If + * the <code>Response</code> has committed the <code>reset</code> + * method will not work in resetting content already written. + * + * @throws IOException thrown if there is a problem resetting + */ + public void reset() throws IOException { + response.reset(); + } + + /** + * This is used to close the connection and commit the request. + * This provides the same semantics as closing the output stream + * and ensures that the HTTP response is committed. This will + * throw an exception if the response can not be committed. + * + * @throws IOException thrown if there is a problem writing + */ + public void close() throws IOException { + response.close(); + } + + /** + * This method returns a string representing the header that was + * generated for this header. For performance reasons it is better + * to acquire the character sequence representing the header as it + * does not require the allocation on new memory. + * + * @return this returns a string representation of this response + */ + public String toString() { + return response.toString(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Scheme.java b/simple/simple-http/src/main/java/org/simpleframework/http/Scheme.java new file mode 100644 index 0000000..b6df799 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Scheme.java @@ -0,0 +1,136 @@ +/* +* Scheme.java February 2014 +* +* Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> +* +* Licensed 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.simpleframework.http; + +import java.net.URI; + +/** +* The <code>Scheme</code> represents a scheme used for a URI. Here + * only schemes that directly relate to HTTP are provided, which + * includes HTTP/1.1 schemes and WebSocket 1.0 schemes. + * + * @author Niall Gallagher +*/ +public enum Scheme { + + /** + * This represents the scheme for a plaintext HTTP connection. + */ + HTTP("http", false), + + /** + * This represents the scheme for a HTTP over TLS connection. + */ + HTTPS("https", true), + + /** + * This represents the scheme for a plaintext WebSocket connection. + */ + WS("ws", false), + + /** + * This represents the scheme for WebSocket over TLS connection. + */ + WSS("wss", true); + + /** + * This is the actual scheme token that is to be used in the URI. + */ + public final String scheme; + + /** + * This is used to determine if the connection is secure or not. + */ + public final boolean secure; + + /** + * Constructor for the <code>Scheme</code> object. This is used + * create an entry using the specific scheme token and a boolean + * indicating if the scheme is secure or not. + * + * @param scheme this is the scheme token to be used + * @param secure this determines if the scheme is secure or not + */ + private Scheme(String scheme, boolean secure) { + this.scheme = scheme; + this.secure = secure; + } + + /** + * This is used to determine if the scheme is secure or not. In + * general a secure scheme is one sent over a SSL/TLS connection. + * + * @return this returns true if the scheme is a secure one + */ + public boolean isSecure() { + return secure; + } + + /** + * This is used to acquire the scheme token for this. The scheme + * token can be used to prefix a absolute fully qualified URI. + * + * @return the scheme token representing this scheme + */ + public String getScheme() { + return scheme; + } + + /** + * This is used to resolve the scheme given a token. If there is + * no matching scheme for the provided token a default of HTTP + * is provided. + * + * @param token this is the token used to determine the scheme + * + * @return this returns the match or HTTP if none matched + */ + public static Scheme resolveScheme(String token) { + if(token != null) { + for(Scheme scheme : values()) { + if(token.equalsIgnoreCase(scheme.scheme)) { + return scheme; + } + } + } + return HTTP; + } + + /** + * This is used to resolve the scheme given a <code>URI</code>. If + * there is no matching scheme for the provided instance then this + * will return null. + * + * @param token this is the object to resolve a scheme for + * + * @return this returns the match or null if none matched + */ + public static Scheme resolveScheme(URI target) { + if(target != null) { + String scheme = target.getScheme(); + + for(Scheme option : values()) { + if(option.scheme.equalsIgnoreCase(scheme)) { + return option; + } + } + } + return null; + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/Status.java b/simple/simple-http/src/main/java/org/simpleframework/http/Status.java new file mode 100644 index 0000000..7fa3b6f --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/Status.java @@ -0,0 +1,320 @@ +/* + * Status.java February 2008 + * + * Copyright (C) 2008, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +/** + * The <code>Status</code> enumeration is used to specify status codes + * and the descriptions of those status codes. This is a convenience + * enumeration that allows users to acquire the descriptions of codes + * by simply providing the code. Also if the response state is known + * the code and description can be provided to the client. + * <p> + * The official HTTP status codes are defined in RFC 2616 section 10. + * Each set of status codes belongs to a specific family. Each family + * describes a specific scenario. Although it is possible to use other + * status codes it is recommended that servers restrict their status + * code responses to those specified in this enumeration. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.StatusLine + */ +public enum Status { + + /** + * This is used as an intermediate response to a request. + */ + CONTINUE(100, "Continue"), + + /** + * This represents a change in the protocol the client is using. + */ + SWITCHING_PROTOCOLS(101, "Switching Protocols"), + + /** + * This represents a successful response of a targeted request. + */ + OK(200, "OK"), + + /** + * This is used to signify that a resource was created successfully. + */ + CREATED(201, "Created"), + + /** + * This is used to signify that the request has been accepted. + */ + ACCEPTED(202, "Accepted"), + + /** + * This represents a response that contains no response content. + */ + NO_CONTENT(204, "No Content"), + + /** + * This is used to represent a response that resets the content. + */ + RESET_CONTENT(205, "Reset Content"), + + /** + * This is used to represent a response that has partial content. + */ + PARTIAL_CONTENT(206, "Partial Content"), + + /** + * This is used to represent a response where there are choices. + */ + MULTIPLE_CHOICES(300, "Multiple Choices"), + + /** + * This is used to represent a target resource that has moved. + */ + MOVED_PERMANENTLY(301, "Moved Permanently"), + + /** + * This is used to represent a resource that has been found. + */ + FOUND(302, "Found"), + + /** + * This is used to tell the client to see another HTTP resource. + */ + SEE_OTHER(303, "See Other"), + + /** + * This is used in response to a target that has not been modified. + */ + NOT_MODIFIED(304, "Not Modified"), + + /** + * This is used to tell the client that it should use a proxy. + */ + USE_PROXY(305, "Use Proxy"), + + /** + * This is used to redirect the client to a resource that has moved. + */ + TEMPORARY_REDIRECT(307, "Temporary Redirect"), + + /** + * This is used to tell the client they have send an invalid request. + */ + BAD_REQUEST(400, "Bad Request"), + + /** + * This is used to tell the client that authorization is required. + */ + UNAUTHORIZED(401, "Unauthorized"), + + /** + * This is used to tell the client that payment is required. + */ + PAYMENT_REQUIRED(402, "Payment Required"), + + /** + * This is used to tell the client that the resource is forbidden. + */ + FORBIDDEN(403, "Forbidden"), + + /** + * This is used to tell the client that the resource is not found. + */ + NOT_FOUND(404, "Not Found"), + + /** + * This is used to tell the client that the method is not allowed. + */ + METHOD_NOT_ALLOWED(405, "Method Not Allowed"), + + /** + * This is used to tell the client the request is not acceptable. + */ + NOT_ACCEPTABLE(406, "Not Acceptable"), + + /** + * This is used to tell the client that authentication is required. + */ + PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"), + + /** + * This is used to tell the client that the request has timed out. + */ + REQUEST_TIMEOUT(408, "Request Timeout"), + + /** + * This is used to tell the client that there has been a conflict. + */ + CONFLICT(409, "Conflict"), + + /** + * This is used to tell the client that the resource has gone. + */ + GONE(410, "Gone"), + + /** + * This is used to tell the client that a request length is needed. + */ + LENGTH_REQUIRED(411, "Length Required"), + + /** + * This is used to tell the client that a precondition has failed. + */ + PRECONDITION_FAILED(412, "Precondition Failed"), + + /** + * This is used to tell the client that the request body is too big. + */ + REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"), + + /** + * This is used to tell the client that the request URI is too long. + */ + REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"), + + /** + * This is used to tell the client that the content type is invalid. + */ + UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), + + /** + * This is used to tell the client that the range is invalid. + */ + REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), + + /** + * This is used to tell the client that the expectation has failed. + */ + EXPECTATION_FAILED(417, "Expectation Failed"), + + /** + * This is sent when the request has caused an internal server error. + */ + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), + + /** + * This is used to tell the client the resource is not implemented. + */ + NOT_IMPLEMENTED(501, "Not Implemented"), + + /** + * This is used to tell the client that the gateway is invalid. + */ + BAD_GATEWAY(502, "Bad Gateway"), + + /** + * This is used to tell the client the resource is unavailable. + */ + SERVICE_UNAVAILABLE(503, "Service Unavailable"), + + /** + * This is used to tell the client there was a gateway timeout. + */ + GATEWAY_TIMEOUT(504, "Gateway Timeout"), + + /** + * This is used to tell the client the request version is invalid. + */ + VERSION_NOT_SUPPORTED(505, "Version Not Supported"); + + /** + * This is the description of the status this instance represents. + */ + public final String description; + + /** + * This is the code for the status that this instance represents. + */ + public final int code; + + /** + * Constructor for the <code>Status</code> object. This will create + * a status object that is used to represent a response state. It + * contains a status code and a description of that code. + * + * @param code this is the code that is used for this status + * @param description this is the description used for the status + */ + private Status(int code, String description) { + this.description = description; + this.code = code; + } + + /** + * This is used to acquire the code of the status object. This is + * used in the HTTP response message to tell the client what kind + * of response this represents. Typically this is used to get a + * code for a known response state for convenience. + * + * @return the code associated by this status instance + */ + public int getCode() { + return code; + } + + /** + * This is used to provide the status description. The description + * is the textual description of the response state. It is used + * so that the response can be interpreted and is a required part + * of the HTTP response combined with the status code. + * + * @return the description associated by this status instance + */ + public String getDescription() { + return description; + } + + /** + * This is used to provide the status description. The description + * is the textual description of the response state. It is used + * so that the response can be interpreted and is a required part + * of the HTTP response combined with the status code. + * + * @param code this is the code to resolve the description for + * + * @return the description associated by this status code + */ + public static String getDescription(int code) { + Status[] list = values(); + + for(Status status : list) { + if(status.code == code) + return status.description; + } + return "Unknown"; + } + + /** + * This is used to provide the status value. If the specified + * code can not be matched this will return the default HTTP/1.1 + * status code of OK, which may not match the intended status. + * + * @param code this is the code to resolve the status for + * + * @return the status value associated by this status code + */ + public static Status getStatus(int code) { + Status[] list = values(); + + for(Status status : list) { + if(status.code == code) + return status; + } + return OK; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/StatusLine.java b/simple/simple-http/src/main/java/org/simpleframework/http/StatusLine.java new file mode 100644 index 0000000..3ea0f16 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/StatusLine.java @@ -0,0 +1,122 @@ +/* + * StatusLine.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http; + +/** + * The <code>StatusLine</code> is used to represent a HTTP status + * line. This provides several convenience methods that can be used + * to manipulate a HTTP status line. see the RFC (RFC 2616) for the + * syntax of a status line. + * + * @author Niall Gallagher + */ +public interface StatusLine { + + /** + * This represents the status code of the HTTP response. + * The response code represents the type of message that is + * being sent to the client. For a description of the codes + * see RFC 2616 section 10, Status Code Definitions. + * + * @return the status code that this HTTP response has + */ + int getCode(); + + /** + * This method allows the status for the response to be + * changed. This MUST be reflected the the response content + * given to the client. For a description of the codes see + * RFC 2616 section 10, Status Code Definitions. + * + * @param code the new status code for the HTTP response + */ + void setCode(int code); + + /** + * This can be used to retrieve the text of a HTTP status + * line. This is the text description for the status code. + * This should match the status code specified by the RFC. + * + * @return the message description of the response + */ + String getDescription(); + + /** + * This is used to set the text of the HTTP status line. + * This should match the status code specified by the RFC. + * + * @param text the descriptive text message of the status + */ + void setDescription(String text); + + /** + * This is used to acquire the status from the response. + * The <code>Status</code> object returns represents the + * code that has been set on the response, it does not + * necessarily represent the description in the response. + * + * @return this is the response for this status line + */ + Status getStatus(); + + /** + * This is used to set the status code and description + * for this response. Setting the code and description in + * this manner provides a much more convenient way to set + * the response status line details. + * + * @param status this is the status to set on the response + */ + void setStatus(Status status); + + /** + * This can be used to get the major number from a HTTP + * version. The major version corresponds to the major + * type that is the 1 of a HTTP/1.0 version string. + * + * @return the major version number for the response + */ + int getMajor(); + + /** + * This can be used to specify the major version. This + * should be the major version of the HTTP request. + * + * @param major this is the major number desired + */ + void setMajor(int major); + + /** + * This can be used to get the minor number from a HTTP + * version. The major version corresponds to the minor + * type that is the 0 of a HTTP/1.0 version string. + * + * @return the major version number for the response + */ + int getMinor(); + + /** + * This can be used to specify the minor version. This + * should not be set to zero if the HTTP request was + * for HTTP/1.1. The response must be equal or higher. + * + * @param minor this is the minor number desired + */ + void setMinor(int minor); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyEncoder.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyEncoder.java new file mode 100644 index 0000000..a2cd6dd --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyEncoder.java @@ -0,0 +1,108 @@ +/* + * BodyEncoder.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * The <code>BodyEncoder</code> object is used to encode content from + * the HTTP response. This acts in much the same way as an output + * stream would. As a requirement of RFC 2616 any HTTP/1.1 compliant + * server must support a set of transfer types. These are fixed size, + * chunked encoded, and connection close. A producer implementation + * is required to implement one of this formats for delivery of the + * response message. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.core.BodyObserver + */ +interface BodyEncoder { + + /** + * This method is used to encode the provided array of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param array this is the array of bytes to send to the client + */ + void encode(byte[] array) throws IOException; + + /** + * This method is used to encode the provided array of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param array this is the array of bytes to send to the client + * @param off this is the offset within the array to send from + * @param size this is the number of bytes that are to be sent + */ + void encode(byte[] array, int off, int size) throws IOException; + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + */ + void encode(ByteBuffer buffer) throws IOException; + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + * @param off this is the offset within the buffer to send from + * @param size this is the number of bytes that are to be sent + */ + void encode(ByteBuffer buffer, int off, int size) throws IOException; + + /** + * This method is used to flush the contents of the buffer to + * the client. This method will block until such time as all of + * the data has been sent to the client. If at any point there + * is an error sending the content an exception is thrown. + */ + void flush() throws IOException; + + /** + * This is used to signal to the producer that all content has + * been written and the user no longer needs to write. This will + * either close the underlying transport or it will notify the + * monitor that the response has completed and the next request + * can begin. This ensures the content is flushed to the client. + */ + void close() throws IOException; +} + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyEncoderException.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyEncoderException.java new file mode 100644 index 0000000..7a0a86a --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyEncoderException.java @@ -0,0 +1,58 @@ +/* + * BodyEncoderException.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; + +/** + * The <code>BodyEncoderException</code> object is used to represent + * an exception that is thrown when there is a problem producing the + * response body. This can be used to wrap <code>IOException</code> + * objects that are thrown from the underlying transport. + * + * @author Niall Gallagher + */ +class BodyEncoderException extends IOException { + + /** + * Constructor for the <code>BodyEncoderException</code> object. This + * is used to represent an exception that is thrown when producing + * the response body. The encoder exception is an I/O exception + * and thus exceptions can propagate out of stream methods. + * + * @param message this is the message describing the exception + */ + public BodyEncoderException(String message) { + super(message); + } + + /** + * Constructor for the <code>BodyEncoderException</code> object. This + * is used to represent an exception that is thrown when producing + * the response body. The encoder exception is an I/O exception + * and thus exceptions can propagate out of stream methods. + * + * @param message this is the message describing the exception + * @param cause this is the cause of the encoder exception + */ + public BodyEncoderException(String message, Throwable cause) { + super(message); + initCause(cause); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyEncoderFactory.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyEncoderFactory.java new file mode 100644 index 0000000..93aab52 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyEncoderFactory.java @@ -0,0 +1,118 @@ +/* + * BodyEncoderFactory.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteWriter; + +/** + * The <code>BodyEncoderFactory</code> is used to create a producer to + * match the HTTP header sent with the response. This interprets the + * headers within the response and composes a producer that will + * match those. Producers can be created to send in chunked encoding + * format, as well as fixed length and connection close for HTTP/1.0. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.core.ResponseEncoder + */ +class BodyEncoderFactory { + + /** + * This is used to determine the semantics of the HTTP pipeline. + */ + private final Conversation support; + + /** + * This is the monitor used to notify the initiator of events. + */ + private final BodyObserver observer; + + /** + * This is the underlying sender used to deliver the raw data. + */ + private final ByteWriter writer; + + /** + * Constructor for the <code>BodyEncoderFactory</code> object. + * This is used to create producers that can encode data in a HTTP + * compliant format. Each producer created will produce its data + * and deliver it to the specified sender, should an I/O events + * occur such as an error, or completion of the response then + * the monitor is notified and the server kernel takes action. + * + * @param observer this is used to deliver signals to the kernel + * @param support this contains details regarding the semantics + * @param writer this is used to send to the underlying transport + */ + public BodyEncoderFactory(BodyObserver observer, Conversation support, Channel channel) { + this.writer = channel.getWriter(); + this.observer = observer; + this.support = support; + } + + /** + * This is used to create an a <code>BodyEncoder</code> object + * that can be used to encode content according to the HTTP header. + * If the request was from a HTTP/1.0 client that did not ask + * for keep alive connection semantics a simple close producer + * is created. Otherwise the content is chunked encoded or sent + * according the the Content-Length. + * + * @return this returns the producer used to send the response + */ + public BodyEncoder getInstance() { + boolean keepAlive = support.isKeepAlive(); + boolean chunkable = support.isChunkedEncoded(); + boolean tunnel = support.isTunnel(); + + if(!keepAlive || tunnel) { + return new CloseEncoder(observer, writer); + } + return getInstance(chunkable); + } + + /** + * This is used to create an a <code>BodyEncoder</code> object + * that can be used to encode content according to the HTTP header. + * If the request was from a HTTP/1.0 client that did not ask + * for keep alive connection semantics a simple close producer + * is created. Otherwise the content is chunked encoded or sent + * according the the Content-Length. + * + * @param chunkable does the connected client support chunked + * + * @return this returns the producer used to send the response + */ + private BodyEncoder getInstance(boolean chunkable) { + long length = support.getContentLength(); + + if(!support.isHead()) { + if(length > 0) { + return new FixedLengthEncoder(observer, writer, length); + } + if(chunkable) { + return new ChunkedEncoder(observer, writer); + } + } + return new EmptyEncoder(observer, writer); + } +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyObserver.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyObserver.java new file mode 100644 index 0000000..eebefc7 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/BodyObserver.java @@ -0,0 +1,121 @@ +/* + * BodyObserver.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import org.simpleframework.transport.ByteWriter; + +/** + * The <code>BodyObserver</code> object is core to how the requests + * are processed from a pipeline. This observes the progress of the + * response streams as they are written to the underlying transport + * which is typically TCP. If at any point there is an error in + * the delivery of the response the observer is notified. It can + * then shutdown the connection, as RFC 2616 suggests on errors. + * <p> + * If however the response is delivered successfully the monitor is + * notified of this event. On successful delivery the monitor will + * hand the <code>Channel</code> back to the server kernel so that + * the next request can be processed. This ensures ordering of the + * responses matches ordering of the requests. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.core.Controller + */ +interface BodyObserver { + + /** + * This is used to close the underlying transport. A closure is + * typically done when the response is to a HTTP/1.0 client + * that does not require a keep alive connection. Also, if the + * container requests an explicit closure this is used when all + * of the content for the response has been sent. + * + * @param writer this is the writer used to send the response + */ + void close(ByteWriter writer); + + /** + * This is used when there is an error sending the response. On + * error RFC 2616 suggests a connection closure is the best + * means to handle the condition, and the one clients should be + * expecting and support. All errors result in closure of the + * underlying transport and no more requests are processed. + * + * @param writer this is the writer used to send the response + */ + void error(ByteWriter writer); + + /** + * This is used when the response has been sent correctly and + * the connection supports persisted HTTP. When ready the channel + * is handed back in to the server kernel where the next request + * on the pipeline is read and used to compose the next entity. + * + * @param writer this is the writer used to send the response + */ + void ready(ByteWriter writer); + + /** + * This is used to notify the monitor that the HTTP response is + * committed and that the header can no longer be changed. It + * is also used to indicate whether the response can be reset. + * + * @param writer this is the writer used to send the response + */ + void commit(ByteWriter writer); + + /** + * This can be used to determine whether the response has been + * committed. If the response is committed then the header can + * no longer be manipulated and the response has been partially + * send to the client. + * + * @return true if the response headers have been committed + */ + boolean isCommitted(); + + /** + * This is used to determine if the response has completed or + * if there has been an error. This basically allows the writer + * of the response to take action on certain I/O events. + * + * @return this returns true if there was an error or close + */ + boolean isClosed(); + + /** + * This is used to determine if the response was in error. If + * the response was in error this allows the writer to throw an + * exception indicating that there was a problem responding. + * + * @return this returns true if there was a response error + */ + boolean isError(); + + /** + * This represents the time at which the response was either + * ready, closed or in error. Providing a time here is useful + * as it allows the time taken to generate a response to be + * determined even if the response is written asynchronously. + * + * @return the time when the response completed or failed + */ + long getTime(); +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ChunkedEncoder.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ChunkedEncoder.java new file mode 100644 index 0000000..5663d4b --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ChunkedEncoder.java @@ -0,0 +1,221 @@ +/* + * ChunkedEncoder.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.simpleframework.transport.ByteWriter; + +/** + * The <code>ChunkedEncoder</code> object is used to encode data in + * the chunked encoding format. A chunked producer is required when + * the length of the emitted content is unknown. It enables the HTTP + * pipeline to remain open as it is a self delimiting format. This + * is preferred over the <code>CloseEncoder</code> for HTTP/1.1 as + * it maintains the pipeline and thus the cost of creating it. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.message.ChunkedConsumer + */ +class ChunkedEncoder implements BodyEncoder { + + /** + * This is the size line which is used to generate the size. + */ + private byte[] size = { '0', '0', '0', '0', '0', '0', '0', '0', '\r', '\n' }; + + /** + * This is the hexadecimal alphabet used to translate the size. + */ + private byte[] index = { '0', '1', '2', '3', '4', '5','6', '7', '8', '9', 'a', 'b', 'c', 'd','e', 'f' }; + + /** + * This is the zero length chunk sent when this is completed. + */ + private byte[] zero = { '0', '\r', '\n', '\r', '\n' }; + + /** + * This is the observer used to notify the selector of events. + */ + private BodyObserver observer; + + /** + * This is the underlying writer used to deliver the encoded data. + */ + private ByteWriter writer; + + /** + * Constructor for the <code>ChunkedEncoder</code> object. This + * is used to create a producer that can sent data in the chunked + * encoding format. Once the data is encoded in the format it is + * handed to the provided <code>ByteWriter</code> object which will + * then deliver it to the client using the underlying transport. + * + * @param observer this is the observer used to signal I/O events + * @param writer this is the writer used to deliver the content + */ + public ChunkedEncoder(BodyObserver observer, ByteWriter writer) { + this.observer = observer; + this.writer = writer; + } + + /** + * This method is used to encode the provided array of bytes in + * a HTTP/1.1 complaint format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param array this is the array of bytes to send to the client + */ + public void encode(byte[] array) throws IOException { + encode(array, 0, array.length); + } + + /** + * This method is used to encode the provided array of bytes in + * a HTTP/1.1 complaint format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param array this is the array of bytes to send to the client + * @param off this is the offset within the array to send from + * @param len this is the number of bytes that are to be sent + */ + public void encode(byte[] array, int off, int len) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(array, off, len); + + if(len > 0) { + encode(buffer); + } + } + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + */ + public void encode(ByteBuffer buffer) throws IOException { + int mark = buffer.position(); + int size = buffer.limit(); + + if(mark > size) { + throw new BodyEncoderException("Buffer position greater than limit"); + } + encode(buffer, 0, size - mark); + } + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + * @param off this is the offset within the buffer to send from + * @param len this is the number of bytes that are to be sent + */ + public void encode(ByteBuffer buffer, int off, int len) throws IOException { + int pos = 7; + + if(observer.isClosed()) { + throw new BodyEncoderException("Stream has been closed"); + } + if(len > 0) { + for(int num = len; num > 0; num >>>= 4){ + size[pos--] = index[num & 0xf]; + } + try { + writer.write(size, pos + 1, 9 - pos); + writer.write(buffer, off, len); + writer.write(size, 8, 2); + } catch(Exception cause) { + if(writer != null) { + observer.error(writer); + } + throw new BodyEncoderException("Error sending response", cause); + } + } + } + + /** + * This method is used to flush the contents of the buffer to + * the client. This method will block until such time as all of + * the data has been sent to the client. If at any point there + * is an error sending the content an exception is thrown. + */ + public void flush() throws IOException { + try { + if(!observer.isClosed()) { + writer.flush(); + } + } catch(Exception cause) { + if(writer != null) { + observer.close(writer); + } + throw new BodyEncoderException("Error sending response", cause); + } + } + + /** + * This method is used to write the zero length chunk. Writing + * the zero length chunk tells the client that the response has + * been fully sent, and the next sequence of bytes from the HTTP + * pipeline is the start of the next response. This will signal + * to the server kernel that the next request is read to read. + */ + private void finish() throws IOException { + try { + writer.write(zero); + observer.ready(writer); + } catch(Exception cause) { + if(writer != null) { + observer.close(writer); + } + throw new BodyEncoderException("Error flushing response", cause); + } + } + + /** + * This is used to signal to the producer that all content has + * been written and the user no longer needs to write. This will + * either close the underlying transport or it will notify the + * monitor that the response has completed and the next request + * can begin. This ensures the content is flushed to the client. + */ + public void close() throws IOException { + if(!observer.isClosed()) { + finish(); + } + } +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/CloseEncoder.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/CloseEncoder.java new file mode 100644 index 0000000..8abd726 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/CloseEncoder.java @@ -0,0 +1,179 @@ +/* + * CloseEncoder.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.simpleframework.transport.ByteWriter; + +/** + * The <code>CloseEncoder</code> is used to close a connection once + * all of the content has been produced. This is typically used if + * the connected client supports the HTTP/1.0 protocol and there is + * no Connection header with the keep-alive token. For reasons of + * performance this should not be used for HTTP/1.1 clients. + * + * @author Niall Gallagher + */ +class CloseEncoder implements BodyEncoder { + + /** + * This is the observer used to notify the selector of events. + */ + private final BodyObserver observer; + + /** + * This is the underlying writer used to deliver the raw data. + */ + private final ByteWriter writer; + + /** + * Constructor for the <code>CloseEncoder</code> object. This is + * used to create a producer that will close the underlying socket + * as a means to signal that the response is fully sent. This is + * typically used with HTTP/1.0 connections. + * + * @param writer this is used to send to the underlying transport + * @param observer this is used to deliver signals to the kernel + */ + public CloseEncoder(BodyObserver observer, ByteWriter writer) { + this.observer = observer; + this.writer = writer; + } + + /** + * This method is used to encode the provided array of bytes in + * a HTTP/1.1 complaint format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param array this is the array of bytes to send to the client + */ + public void encode(byte[] array) throws IOException { + encode(array, 0, array.length); + } + + /** + * This method is used to encode the provided array of bytes in + * a HTTP/1.1 complaint format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param array this is the array of bytes to send to the client + * @param off this is the offset within the array to send from + * @param len this is the number of bytes that are to be sent + */ + public void encode(byte[] array, int off, int len) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(array, off, len); + + if(len > 0) { + encode(buffer); + } + } + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + */ + public void encode(ByteBuffer buffer) throws IOException { + int mark = buffer.position(); + int size = buffer.limit(); + + if(mark > size) { + throw new BodyEncoderException("Buffer position greater than limit"); + } + encode(buffer, 0, size - mark); + } + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + * @param off this is the offset within the buffer to send from + * @param len this is the number of bytes that are to be sent + */ + public void encode(ByteBuffer buffer, int off, int len) throws IOException { + if(observer.isClosed()) { + throw new BodyEncoderException("Stream has been closed"); + } + try { + writer.write(buffer, off, len); + } catch(Exception cause) { + if(writer != null) { + observer.error(writer); + } + throw new BodyEncoderException("Error sending response", cause); + } + } + + /** + * This method is used to flush the contents of the buffer to + * the client. This method will block until such time as all of + * the data has been sent to the client. If at any point there + * is an error sending the content an exception is thrown. + */ + public void flush() throws IOException { + try { + if(!observer.isClosed()) { + writer.flush(); + } + } catch(Exception cause) { + if(writer != null) { + observer.error(writer); + } + throw new BodyEncoderException("Error sending response", cause); + } + } + + /** + * This is used to signal to the producer that all content has + * been written and the user no longer needs to write. This will + * close the underlying transport which tells the client that + * all of the content has been sent over the connection. + */ + public void close() throws IOException { + try { + if(!observer.isClosed()) { + observer.close(writer); + writer.close(); + } + } catch(Exception cause) { + if(writer != null) { + observer.error(writer); + } + throw new BodyEncoderException("Error sending response", cause); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/Collector.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/Collector.java new file mode 100644 index 0000000..df5b4ca --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/Collector.java @@ -0,0 +1,50 @@ +/* + * Collector.java October 2002 + * + * Copyright (C) 2002, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; + +import org.simpleframework.http.message.Entity; + +/** + * The <code>Collector</code> object is used to collect all of the + * data used to form a request entity. This will collect the data + * fragment by fragment from the underlying transport. When all + * of the data is consumed and the entity is created and then it + * is sent to the <code>Controller</code> object for processing. + * If the request has completed the next request can be collected + * from the underlying transport using a new collector object. + * + * @author Niall Gallagher + */ +interface Collector extends Entity { + + /** + * This is used to collect the data from a <code>Channel</code> + * which is used to compose the entity. If at any stage there + * are no ready bytes on the socket the controller provided can be + * used to queue the collector until such time as the socket is + * ready to read. Also, should the entity have completed reading + * all required content it is handed to the controller as ready, + * which processes the entity as a new client HTTP request. + * + * @param controller this is the controller used to queue this + */ + void collect(Controller controller) throws IOException; +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/Container.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/Container.java new file mode 100644 index 0000000..91a034c --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/Container.java @@ -0,0 +1,62 @@ +/* + * Container.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +/** + * The <code>Container</code> object is used to process HTTP requests + * and compose HTTP responses. The <code>Request</code> objects that + * are handed to this container contain all information relating to + * the received message. The responsibility of the container is to + * interpret the request and compose a suitable response. + * <p> + * All implementations must ensure that the container is thread safe + * as it will receive multiple HTTP transactions concurrently. Also + * it should be known that the <code>Response</code> object used to + * deliver the HTTP response will only commit and send once it has + * its <code>OutputStream</code> closed. + * <p> + * The <code>Container</code> is entirely responsible for the HTTP + * message headers and body. It is up to the implementation to ensure + * that it complies to RFC 2616 or any previous specification. All + * headers and the status line can be modified by this object. + * + * @author Niall Gallagher + */ +public interface Container { + + /** + * Used to pass the <code>Request</code> and <code>Response</code> + * to the container for processing. Any implementation of this + * must ensure that this is thread safe, as it will receive many + * concurrent invocations each with a unique HTTP request. + * <p> + * The request and response objects are used to interact with the + * connected pipeline, in such a way that requests and response + * objects can be delivered in sequence and without interference. + * The next request from a HTTP pipeline is only processed once + * the <code>Response</code> object has been closed and committed. + * + * @param req the request that contains the client HTTP message + * @param resp the response used to deliver the server response + */ + void handle(Request req, Response resp); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerController.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerController.java new file mode 100644 index 0000000..16a6374 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerController.java @@ -0,0 +1,161 @@ +/* + * ContainerController.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static java.nio.channels.SelectionKey.OP_READ; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.thread.ConcurrentExecutor; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.TransportException; +import org.simpleframework.transport.reactor.ExecutorReactor; +import org.simpleframework.transport.reactor.Reactor; + +/** + * The <code>ContainerController</code> object is essentially the core + * processing engine for the server. This is used to collect requests + * from the connected channels and dispatch those requests to the + * provided <code>Container</code> object. This contains two thread + * pools. The first is used to collect data from the channels and + * create request entities. The second is used to take the created + * entities and service them with the provided container. + * + * @author Niall Gallagher + */ +class ContainerController implements Controller { + + /** + * This is the thread pool used for servicing the requests. + */ + private final ConcurrentExecutor executor; + + /** + * This is the thread pool used for collecting the requests. + */ + private final ConcurrentExecutor collect; + + /** + * This is the allocator used to create the buffers needed. + */ + private final Allocator allocator; + + /** + * This is the container used to service the requests. + */ + private final Container container; + + /** + * This is the reactor used to schedule the collectors. + */ + private final Reactor reactor; + + /** + * Constructor for the <code>ContainerController</code> object. This + * is used to create a controller which will collect and dispatch + * requests using two thread pools. The first is used to collect + * the requests, the second is used to service those requests. + * + * @param container this is the container used to service requests + * @param allocator this is used to allocate any buffers needed + * @param count this is the number of threads per thread pool + * @param select this is the number of controller threads to use + */ + public ContainerController(Container container, Allocator allocator, int count, int select) throws IOException { + this.executor = new ConcurrentExecutor(RequestDispatcher.class, count); + this.collect = new ConcurrentExecutor(RequestReader.class, count); + this.reactor = new ExecutorReactor(collect, select); + this.allocator = allocator; + this.container = container; + } + + /** + * This is used to initiate the processing of the channel. Once + * the channel is passed in to the initiator any bytes ready on + * the HTTP pipeline will be processed and parsed in to a HTTP + * request. When the request has been built a callback is made + * to the <code>Container</code> to process the request. Also + * when the request is completed the channel is passed back in + * to the initiator so that the next request can be dealt with. + * + * @param channel the channel to process the request from + */ + public void start(Channel channel) throws IOException { + start(new RequestCollector(allocator, channel)); + } + + /** + * The start event is used to immediately consume bytes form the + * underlying transport, it does not require a select to check + * if the socket is read ready which improves performance. Also, + * when a response has been delivered the next request from the + * pipeline is consumed immediately. + * + * @param collector this is the collector used to collect data + */ + public void start(Collector collector) throws IOException { + reactor.process(new RequestReader(this, collector)); + } + + /** + * The select event is used to register the connected socket with + * a Java NIO selector which can efficiently determine when there + * are bytes ready to read from the socket. + * + * @param collector this is the collector used to collect data + */ + public void select(Collector collector) throws IOException { + reactor.process(new RequestReader(this, collector), OP_READ); + } + + /** + * The ready event is used when a full HTTP entity has been + * collected from the underlying transport. On such an event the + * request and response can be handled by a container. + * + * @param collector this is the collector used to collect data + */ + public void ready(Collector collector) throws IOException { + executor.execute(new RequestDispatcher(container, this, collector)); + } + + /** + * This method is used to stop the <code>Selector</code> so that + * all resources are released. As well as freeing occupied memory + * this will also stop all threads, which means that is can no + * longer be used to collect data from the pipelines. + * <p> + * Here we stop the <code>Reactor</code> first, this ensures + * that there are no further selects performed if a given socket + * does not have enough data to fulfil a request. From there we + * stop the main dispatch <code>Executor</code> so that all of + * the currently executing tasks complete. The final stage of + * termination requires the collector thread pool to be stopped. + */ + public void stop() throws IOException { + try { + reactor.stop(); + executor.stop(); + collect.stop(); + } catch(Exception cause) { + throw new TransportException("Error stopping", cause); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerEvent.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerEvent.java new file mode 100644 index 0000000..ecd96a3 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerEvent.java @@ -0,0 +1,93 @@ +/* + * ContainerEvent.java October 2012 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +/** + * The <code>ContainerEvent</code> enum represents events that occur when + * processing a HTTP transaction. Here each phase of processing has a + * single event to represent it. If a <code>Trace</code> object has been + * associated with the connection then the server will notify the trace + * when the connection enters a specific phase of processing. + * + * @author Niall Gallagher + * + * @see org.simpleframework.transport.trace.Trace + */ +public enum ContainerEvent { + + /** + * This event indicates that the server is reading the request header. + */ + READ_HEADER, + + /** + * This event indicates that the server is reading the request body. + */ + READ_BODY, + + /** + * This event indicates that the server is writing the response header. + */ + WRITE_HEADER, + + /** + * This event indicates that the server is writing the response body. + */ + WRITE_BODY, + + /** + * This indicates that the server has fully read the request header. + */ + HEADER_FINISHED, + + /** + * This indicates that the server has fully read the request body. + */ + BODY_FINISHED, + + /** + * This event indicates that the server sent a HTTP continue reply. + */ + DISPATCH_CONTINUE, + + /** + * This event indicates that the request is ready for processing. + */ + REQUEST_READY, + + /** + * This indicates that the request has been dispatched for processing. + */ + DISPATCH_REQUEST, + + /** + * This indicates that the dispatch thread has completed the dispatch. + */ + DISPATCH_FINISHED, + + /** + * This indicates that all the bytes within the response are sent. + */ + RESPONSE_FINISHED, + + /** + * This indicates that there was some error event with the request. + */ + ERROR; +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerSocketProcessor.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerSocketProcessor.java new file mode 100644 index 0000000..0bf1a44 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerSocketProcessor.java @@ -0,0 +1,155 @@ +/* + * ContainerSocketProcessor.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.FileAllocator; +import org.simpleframework.transport.TransportProcessor; +import org.simpleframework.transport.TransportSocketProcessor; +import org.simpleframework.transport.SocketProcessor; +import org.simpleframework.transport.Socket; + +/** + * The <code>ContainerSocketProcessor</code> object is a connector + * that dispatch requests from a connected pipeline. SSL connections + * and plain connections can be processed by this implementation. It + * collects data from the connected pipelines and constructs the + * requests and responses used to dispatch to the container. + * <p> + * In order to process the requests this uses two thread pools. One + * is used to collect data from the pipelines and create the requests. + * The other is used to service those requests. Such an architecture + * ensures that the serving thread does not have to deal with I/O + * operations. All data is consumed before it is serviced. + * + * @author Niall Gallagher + */ +public class ContainerSocketProcessor implements SocketProcessor { + + /** + * This is the transporter used to process the connections. + */ + private final TransportProcessor processor; + + /** + * This is used to deliver pipelines to the container. + */ + private final SocketProcessor adapter; + + /** + * Constructor for the <code>ContainerSocketProcessor</code> object. + * The connector created will collect HTTP requests from the pipelines + * provided and dispatch those requests to the provided container. + * + * @param container this is the container used to service requests + */ + public ContainerSocketProcessor(Container container) throws IOException { + this(container, 8); + } + + /** + * Constructor for the <code>ContainerSocketProcessor</code> object. + * The connector created will collect HTTP requests from the pipelines + * provided and dispatch those requests to the provided container. + * + * @param container this is the container used to service requests + * @param count this is the number of threads used for each pool + */ + public ContainerSocketProcessor(Container container, int count) throws IOException { + this(container, count, 1); + } + + /** + * Constructor for the <code>ContainerSocketProcessor</code> object. The + * connector created will collect HTTP requests from the pipelines + * provided and dispatch those requests to the provided container. + * + * @param container this is the container used to service requests + * @param count this is the number of threads used for each pool + * @param select this is the number of selector threads to use + */ + public ContainerSocketProcessor(Container container, int count, int select) throws IOException { + this(container, new FileAllocator(), count, select); + } + + /** + * Constructor for the <code>ContainerSocketProcessor</code> object. + * The connector created will collect HTTP requests from the pipelines + * provided and dispatch those requests to the provided container. + * + * @param container this is the container used to service requests + * @param allocator this is the allocator used to create buffers + */ + public ContainerSocketProcessor(Container container, Allocator allocator) throws IOException { + this(container, allocator, 8); + } + + /** + * Constructor for the <code>ContainerSocketProcessor</code> object. + * The connector created will collect HTTP requests from the pipelines + * provided and dispatch those requests to the provided container. + * + * @param container this is the container used to service requests + * @param allocator this is the allocator used to create buffers + * @param count this is the number of threads used for each pool + */ + public ContainerSocketProcessor(Container container, Allocator allocator, int count) throws IOException { + this(container, allocator, count, 1); + } + + /** + * Constructor for the <code>ContainerSocketProcessor</code> object. + * The connector created will collect HTTP requests from the pipelines + * provided and dispatch those requests to the provided container. + * + * @param container this is the container used to service requests + * @param allocator this is the allocator used to create buffers + * @param count this is the number of threads used for each pool + * @param select this is the number of selector threads to use + */ + public ContainerSocketProcessor(Container container, Allocator allocator, int count, int select) throws IOException { + this.processor = new ContainerTransportProcessor(container, allocator, count, select); + this.adapter = new TransportSocketProcessor(processor, count); + } + + /** + * This is used to consume HTTP messages that arrive on the socket + * and dispatch them to the internal container. Depending on whether + * the socket contains an <code>SSLEngine</code> an SSL handshake may + * be performed before any HTTP messages are consumed. This can be + * called from multiple threads and does not block. + * + * @param socket this is the connected HTTP pipeline to process + */ + public void process(Socket socket) throws IOException { + adapter.process(socket); + } + + /** + * This method is used to stop the connector in such a way that it + * will not accept and process any further messages. If there are + * resources to clean up they may be cleaned up asynchronously + * so that this method can return without blocking. + */ + public void stop() throws IOException { + adapter.stop(); + } + } diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerTransportProcessor.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerTransportProcessor.java new file mode 100644 index 0000000..dd6df1a --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ContainerTransportProcessor.java @@ -0,0 +1,96 @@ +/* + * ContainerProcessor.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.transport.TransportProcessor; +import org.simpleframework.transport.Transport; +import org.simpleframework.transport.TransportChannel; + +/** + * The <code>ContainerProcessor</code> object is used to create + * channels which can be used to consume and process requests. This + * is basically an adapter to the <code>Selector</code> which will + * convert the provided transport to a usable channel. Each of the + * connected pipelines will end up at this object, regardless of + * whether those connections are SSL or plain data. + * + * @author Niall Gallagher + */ +public class ContainerTransportProcessor implements TransportProcessor { + + /** + * This is the controller used to process the created channels. + */ + private final Controller controller; + + /** + * Constructor for the <code>ContainerProcessor</code> object. + * This is used to create a processor which will convert the + * provided transport objects to channels, which can then be + * processed by the controller and dispatched to the container. + * + * @param container the container to dispatch requests to + * @param allocator this is the allocator used to buffer data + * @param count this is the number of threads to be used + */ + public ContainerTransportProcessor(Container container, Allocator allocator, int count) throws IOException { + this(container, allocator, count, 1); + } + + /** + * Constructor for the <code>ContainerProcessor</code> object. + * This is used to create a processor which will convert the + * provided transport objects to channels, which can then be + * processed by the controller and dispatched to the container. + * + * @param container the container to dispatch requests to + * @param allocator this is the allocator used to buffer data + * @param count this is the number of threads to be used + * @param select this is the number of controller threads to use + */ + public ContainerTransportProcessor(Container container, Allocator allocator, int count, int select) throws IOException { + this.controller = new ContainerController(container, allocator, count, select); + } + + /** + * This is used to consume HTTP messages that arrive on the given + * transport. All messages consumed from the transport are then + * handed to the <code>Container</code> for processing. The response + * will also be delivered over the provided transport. At this point + * the SSL handshake will have fully completed. + * + * @param transport the transport to process requests from + */ + public void process(Transport transport) throws IOException { + controller.start(new TransportChannel(transport)); + } + + /** + * This method is used to stop the connector in such a way that it + * will not accept and process any further messages. If there are + * resources to clean up they may be cleaned up asynchronously + * so that this method can return without blocking. + */ + public void stop() throws IOException { + controller.stop(); + } + }
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/Controller.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/Controller.java new file mode 100644 index 0000000..3e152bd --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/Controller.java @@ -0,0 +1,100 @@ +/* + * Controller.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; + +import org.simpleframework.transport.Channel; + +/** + * The <code>Controller</code> interface represents an object which + * is used to process collection events. The sequence of events that + * typically take place is for the collection to start, if not all + * of the bytes can be consumed it selects, and finally when all of + * the bytes within the entity have been consumed it is ready. + * <p> + * The start event is used to immediately consume bytes form the + * underlying transport, it does not require a select to determine + * if the socket is read ready which provides an initial performance + * enhancement. Also when a response has been delivered the next + * request from the pipeline is consumed immediately. + * <p> + * The select event is used to register the connected socket with a + * Java NIO selector which can efficiently determine when there are + * bytes ready to read from the socket. Finally, the ready event + * is used when a full HTTP entity has been collected from the + * underlying transport. On such an event the request and response + * can be handled by a container. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.core.Collector + */ +interface Controller { + + /** + * This is used to initiate the processing of the channel. Once + * the channel is passed in to the initiator any bytes ready on + * the HTTP pipeline will be processed and parsed in to a HTTP + * request. When the request has been built a callback is made + * to the <code>Container</code> to process the request. Also + * when the request is completed the channel is passed back in + * to the initiator so that the next request can be dealt with. + * + * @param channel the channel to process the request from + */ + void start(Channel channel) throws IOException; + + /** + * The start event is used to immediately consume bytes form the + * underlying transport, it does not require a select to check + * if the socket is read ready which improves performance. Also, + * when a response has been delivered the next request from the + * pipeline is consumed immediately. + * + * @param collector this is the collector used to collect data + */ + void start(Collector collector) throws IOException; + + /** + * The select event is used to register the connected socket with + * a Java NIO selector which can efficiently determine when there + * are bytes ready to read from the socket. + * + * @param collector this is the collector used to collect data + */ + void select(Collector collector) throws IOException; + + /** + * The ready event is used when a full HTTP entity has been + * collected from the underlying transport. On such an event the + * request and response can be handled by a container. + * + * @param collector this is the collector used to collect data + */ + void ready(Collector collector) throws IOException; + + /** + * This method is used to stop the <code>Selector</code> so that + * all resources are released. As well as freeing occupied memory + * this will also stop all threads, which means that is can no + * longer be used to collect data from the pipelines. + */ + void stop() throws IOException; +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/Conversation.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/Conversation.java new file mode 100644 index 0000000..07318ce --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/Conversation.java @@ -0,0 +1,358 @@ +/* + * Conversation.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static org.simpleframework.http.Method.CONNECT; +import static org.simpleframework.http.Method.HEAD; +import static org.simpleframework.http.Protocol.CHUNKED; +import static org.simpleframework.http.Protocol.CLOSE; +import static org.simpleframework.http.Protocol.CONNECTION; +import static org.simpleframework.http.Protocol.CONTENT_LENGTH; +import static org.simpleframework.http.Protocol.KEEP_ALIVE; +import static org.simpleframework.http.Protocol.TRANSFER_ENCODING; +import static org.simpleframework.http.Protocol.UPGRADE; +import static org.simpleframework.http.Protocol.WEBSOCKET; + +import org.simpleframework.http.RequestHeader; +import org.simpleframework.http.ResponseHeader; + +/** + * The <code>Conversation</code> object is used to set and interpret + * the semantics of the HTTP headers with regard to the encoding + * used for the response. This will ensure the the correct headers + * are used so that if chunked encoding or a connection close is + * needed that the headers are set accordingly. This allows both the + * server and client to agree on the best semantics to use. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.core.ResponseBuffer + * @see org.simpleframework.http.core.ResponseEncoder + */ +public class Conversation { + + /** + * This is the response object that requires HTTP headers set. + */ + private final ResponseHeader response; + + /** + * This contains the request headers and protocol version. + */ + private final RequestHeader request; + + /** + * Constructor for the <code>Conversation</code> object. This is + * used to create an object that makes use of both the request + * and response HTTP headers to determine how best to deliver + * the response body. Depending on the protocol version and the + * existing response headers suitable semantics are determined. + * + * @param request this is the request from the client + * @param response this is the response that is to be sent + */ + public Conversation(RequestHeader request, ResponseHeader response) { + this.response = response; + this.request = request; + } + + /** + * This provides the <code>Request</code> object. This can be + * used to acquire the request HTTP headers and protocl version + * used by the client. Typically the conversation provides all + * the data needed to determine the type of response required. + * + * @return this returns the request object for the conversation + */ + public RequestHeader getRequest() { + return request; + } + + /** + * This provides the <code>Response</code> object. This is used + * when the commit is required on the response. By committing + * the response the HTTP header is generated and delivered to + * the underlying transport. + * + * @return this returns the response for the conversation + */ + public ResponseHeader getResponse() { + return response; + } + + /** + * This is used to acquire the content length for the response. + * The content length is acquired fromt he Content-Length header + * if it has been set. If not then this will return a -1 value. + * + * @return this returns the value for the content length header + */ + public long getContentLength() { + return response.getContentLength(); + } + + /** + * This is used to determine if the <code>Response</code> has a + * message body. If this does not have a message body then true + * is returned. This is determined as of RFC 2616 rules for the + * presence of a message body. A message body must not be + * included with a HEAD request or with a 304 or a 204 response. + * If when this is called there is no message length delimiter + * as specified by section RFC 2616 4.4, then there is no body. + * + * @return true if there is no response body, false otherwise + */ + public boolean isEmpty() { + int code = response.getCode(); + + if(code == 204){ + return true; + } + if(code == 304){ + return true; + } + return false; + } + + /** + * This is used to determine if the request method was HEAD. This + * is of particular interest in a HTTP conversation as it tells + * the response whether a response body is to be sent or not. + * If the method is head the delimeters for the response should + * be as they would be for a similar GET, however no body is sent. + * + * @return true if the request method was a HEAD method + */ + public boolean isHead() { + String method = request.getMethod(); + + if(method != null) { + return method.equalsIgnoreCase(HEAD); + } + return false; + } + + /** + * This is used to determine if the method was a CONNECT. The + * connect method is typically used when a client wishes to + * establish a connection directly with an origin server. Such a + * direct connection is useful when using TLS as it ensures there + * is not man in the middle with respect to key exchanges. + * + * @return this returns true if the method was a CONNECT method + */ + public boolean isConnect() { + String method = request.getMethod(); + + if(method != null) { + return method.equalsIgnoreCase(CONNECT); + } + return false; + } + + /** + * This is used to set the content length for the response. If + * the HTTP version is HTTP/1.1 then the Content-Length header is + * used, if an earlier protocol version is used then connection + * close semantics are also used to ensure client compatibility. + * + * @param length this is the length to set HTTP header to + */ + public void setContentLength(long length) { + boolean keepAlive = isKeepAlive(); + + if(keepAlive) { + response.setValue(CONNECTION, KEEP_ALIVE); + } else { + response.setValue(CONNECTION, CLOSE); + } + response.setLong(CONTENT_LENGTH, length); + } + + /** + * This checks the protocol version used in the request to check + * whether it supports persistent HTTP connections. By default the + * HTTP/1.1 protocol supports persistent connnections, this can + * onlyy be overridden with a Connection header with the close + * token. Earlier protocol versions are connection close. + * + * @return this returns true if the protocol is HTTP/1.1 or above + */ + public boolean isPersistent() { + String token = request.getValue(CONNECTION); + + if(token != null) { + return token.equalsIgnoreCase(KEEP_ALIVE); + } + int major = request.getMajor(); + int minor = request.getMinor(); + + if(major > 1) { + return true; + } + if(major == 1) { + return minor > 0; + } + return false; + } + + /** + * The <code>isKeepAlive</code> method is used to determine if + * the connection semantics are set to maintain the connection. + * This checks to see if there is a Connection header with the + * keep-alive token, if so then the connection is keep alive, if + * however there is no connection header the version is used. + * + * @return true if the response connection is to be maintained + */ + public boolean isKeepAlive() { + String token = response.getValue(CONNECTION); + + if(token != null) { + return !token.equalsIgnoreCase(CLOSE); + } + return isPersistent(); + } + + /** + * The <code>isChunkable</code> method is used to determine if + * the client supports chunked encoding. If the client does not + * support chunked encoding then a connection close should be used + * instead, this allows HTTP/1.0 clients to be supported properly. + * + * @return true if the client supports chunked transfer encoding + */ + public boolean isChunkable() { + int major = request.getMajor(); + int minor = request.getMinor(); + + if(major >= 1) { + return minor >= 1; + } + return false; + } + + /** + * This is used when the output is encoded in the chunked encoding. + * This should only be used if the protocol version is HTTP/1.1 or + * above. If the protocol version supports chunked encoding then it + * will encode the data as specified in RFC 2616 section 3.6.1. + */ + public void setChunkedEncoded() { + boolean keepAlive = isKeepAlive(); + boolean chunkable = isChunkable(); + + if(keepAlive && chunkable) { + response.setValue(TRANSFER_ENCODING, CHUNKED); + response.setValue(CONNECTION, KEEP_ALIVE); + } else { + response.setValue(CONNECTION, CLOSE); + } + } + + /** + * This is used to set the response to a connection upgrade. The + * response for an upgrade contains no payload delimeter such as + * content length or transfer encoding. It is typically used when + * establishing a web socket connection or a HTTP tunnel. + */ + public void setConnectionUpgrade() { + response.setValue(TRANSFER_ENCODING, null); + response.setValue(CONTENT_LENGTH, null); + response.setValue(CONNECTION, UPGRADE); + } + + /** + * This will remove all explicit transfer encoding headers from + * the response header. By default the identity encoding is used + * for all connections, it basically means no encoding. So if the + * response uses a Content-Length it implicitly assumes tha the + * encoding of the response is identity encoding. + */ + public void setIdentityEncoded() { + response.setValue(TRANSFER_ENCODING, null); + } + + /** + * The <code>isChunkedEncoded</code> is used to determine whether + * the chunked encoding scheme is desired. This is enables data to + * be encoded in such a way that a connection can be maintained + * without a Content-Length header. If the output is chunked then + * the connection is keep alive. + * + * @return true if the response output is chunked encoded + */ + public boolean isChunkedEncoded() { + String token = response.getValue(TRANSFER_ENCODING); + + if(token != null) { + return token.equalsIgnoreCase(CHUNKED); + } + return false; + } + + /** + * This is used to determine if a WebSocket upgrade was requested + * and established. An upgrade to use a WebSocket is done when the + * client requests the upgrade and the server responds with an + * upgrade confirmation, this is the basic handshake required. + * + * @return this returns true if a WebSocket handshake succeeded + */ + public boolean isWebSocket() { + String token = request.getValue(UPGRADE); + int code = response.getCode(); + + if(token != null && code == 101) { + String reply = response.getValue(UPGRADE); + + if(token.equalsIgnoreCase(WEBSOCKET)) { + return token.equalsIgnoreCase(reply); + } + } + return false; + } + + /** + * This is used to determine if a tunnel should be established. + * A tunnel is where the the HTTP server no longer processes any + * HTTP requests, it simply forms a byte stream with the client. + * Scenarios where tunnels are useful are when WebSockets are + * required or when a client wants a TLS connection to an origin + * server through a proxy server. + * + * @return this returns true if a tunnel has been established + */ + public boolean isTunnel() { + boolean socket = isWebSocket(); + + if(!socket) { + int code = response.getCode(); + + if(code < 200) { + return false; + } + if(code >= 300) { + return false; + } + return isConnect(); + } + return true; + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/EmptyEncoder.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/EmptyEncoder.java new file mode 100644 index 0000000..7ec78fb --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/EmptyEncoder.java @@ -0,0 +1,132 @@ +/* + * EmptyEncoder.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.simpleframework.transport.ByteWriter; + +/** + * The <code>EmptyEncoder</code> object is a producer used if there + * is not response body to be delivered. Typically this is used when + * the HTTP request method is HEAD or if there is some status code + * sent to the client that does not require a response body. + * + * @author Niall Gallagher + */ +class EmptyEncoder implements BodyEncoder { + + /** + * This is the observer that is used to process the pipeline. + */ + private final BodyObserver observer; + + /** + * This is the writer that is passed to the monitor when ready. + */ + private final ByteWriter writer; + + /** + * Constructor for the <code>EmptyEncoder</code> object. Once + * created this producer will signal the kernel the the next + * request is ready to read from the HTTP pipeline as there is + * no content to be delivered with this producer object. + * + * @param writer this is used to send to the underlying transport + * @param observer this is used to deliver signals to the kernel + */ + public EmptyEncoder(BodyObserver observer, ByteWriter writer) { + this.observer = observer; + this.writer = writer; + } + + /** + * This method performs no operation. Because this producer is + * not required to generate a response body this will ignore all + * data that is provided to sent to the underlying transport. + * + * @param array this is the array of bytes to send to the client + */ + public void encode(byte[] array) throws IOException { + return; + } + + /** + * This method performs no operation. Because this producer is + * not required to generate a response body this will ignore all + * data that is provided to sent to the underlying transport. + * + * @param array this is the array of bytes to send to the client + * @param off this is the offset within the array to send from + * @param size this is the number of bytes that are to be sent + */ + public void encode(byte[] array, int off, int size) throws IOException { + return; + } + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + */ + public void encode(ByteBuffer buffer) throws IOException { + return; + } + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + * @param off this is the offset within the buffer to send from + * @param size this is the number of bytes that are to be sent + */ + public void encode(ByteBuffer buffer, int off, int size) throws IOException { + return; + } + + /** + * This method performs no operation. Because this producer is + * not required to generate a response body this will ignore all + * data that is provided to sent to the underlying transport. + */ + public void flush() throws IOException { + return; + } + + /** + * This method performs no operation. Because this producer is + * not required to generate a response body this will ignore all + * data that is provided to sent to the underlying transport. + */ + public void close() throws IOException { + observer.ready(writer); + } +} + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/FixedLengthEncoder.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/FixedLengthEncoder.java new file mode 100644 index 0000000..86148eb --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/FixedLengthEncoder.java @@ -0,0 +1,198 @@ +/* + * FixedLengthEncoder.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.simpleframework.transport.ByteWriter; + +/** + * The <code>FixedLengthEncoder</code> object produces content without + * any encoding, but limited to a fixed number of bytes. This is used if + * the length of the content being delivered is know beforehand. It + * will simply count the number of bytes being send and signal the + * server kernel that the next request is ready to read once all of + * the bytes have been sent to the client. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.message.FixedLengthConsumer + */ +class FixedLengthEncoder implements BodyEncoder{ + + /** + * This is the observer used to notify the initiator of events. + */ + private BodyObserver observer; + + /** + * This is the underlying writer used to deliver the raw data. + */ + private ByteWriter writer; + + /** + * This is the number of bytes that have been sent so far. + */ + private long count; + + /** + * This is the number of bytes this producer is limited to. + */ + private long limit; + + /** + * Constructor for the <code>FixedLengthEncoder</code> object. This + * is used to create an encoder that will count the number of bytes + * that are sent over the pipeline, once all bytes have been sent + * this will signal that the next request is ready to read. + * + * @param observer this is used to deliver signals to the kernel + * @param writer this is used to send to the underlying transport + * @param limit this is used to limit the number of bytes sent + */ + public FixedLengthEncoder(BodyObserver observer, ByteWriter writer, long limit) { + this.observer = observer; + this.writer = writer; + this.limit = limit; + } + + /** + * This method is used to encode the provided array of bytes in + * a HTTP/1.1 complaint format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param array this is the array of bytes to send to the client + */ + public void encode(byte[] array) throws IOException { + encode(array, 0, array.length); + } + + /** + * This method is used to encode the provided array of bytes in + * a HTTP/1.1 complaint format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param array this is the array of bytes to send to the client + * @param off this is the offset within the array to send from + * @param len this is the number of bytes that are to be sent + */ + public void encode(byte[] array, int off, int len) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(array, off, len); + + if(len > 0) { + encode(buffer); + } + } + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + */ + public void encode(ByteBuffer buffer) throws IOException { + int mark = buffer.position(); + int size = buffer.limit(); + + if(mark > size) { + throw new BodyEncoderException("Buffer position greater than limit"); + } + encode(buffer, 0, size - mark); + } + + /** + * This method is used to encode the provided buffer of bytes in + * a HTTP/1.1 compliant format and sent it to the client. Once + * the data has been encoded it is handed to the transport layer + * within the server, which may choose to buffer the data if the + * content is too small to send efficiently or if the socket is + * not write ready. + * + * @param buffer this is the buffer of bytes to send to the client + * @param off this is the offset within the buffer to send from + * @param len this is the number of bytes that are to be sent + */ + public void encode(ByteBuffer buffer, int off, int len) throws IOException { + long size = Math.min(len, limit - count); + + try { + if(observer.isClosed()) { + throw new BodyEncoderException("Response content complete"); + } + writer.write(buffer, off, (int)size); + + if(count + size == limit) { + observer.ready(writer); + } + } catch(Exception cause) { + if(writer != null) { + observer.error(writer); + } + throw new BodyEncoderException("Error sending response", cause); + } + count += size; + } + + /** + * This method is used to flush the contents of the buffer to + * the client. This method will block until such time as all of + * the data has been sent to the client. If at any point there + * is an error sending the content an exception is thrown. + */ + public void flush() throws IOException { + try { + if(!observer.isClosed()) { + writer.flush(); + } + } catch(Exception cause) { + if(writer != null) { + observer.error(writer); + } + throw new BodyEncoderException("Error flushing", cause); + } + } + + /** + * This is used to signal to the producer that all content has + * been written and the user no longer needs to write. This will + * either close the underlying transport or it will notify the + * monitor that the response has completed and the next request + * can begin. This ensures the content is flushed to the client. + */ + public void close() throws IOException { + if(!observer.isClosed()) { + if(count < limit) { + observer.error(writer); + } else { + observer.ready(writer); + } + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/QueryBuilder.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/QueryBuilder.java new file mode 100644 index 0000000..7942d77 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/QueryBuilder.java @@ -0,0 +1,148 @@ +/* + * QueryBuilder.java October 2002 + * + * Copyright (C) 2002, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static org.simpleframework.http.Protocol.APPLICATION; +import static org.simpleframework.http.Protocol.URL_ENCODED; + +import org.simpleframework.http.ContentType; +import org.simpleframework.http.Query; +import org.simpleframework.http.Request; +import org.simpleframework.http.message.Entity; +import org.simpleframework.http.message.Header; + +/** + * The <code>QueryBuilder</code> object is used to create the query. + * It is created using the request URI query and a form post body if + * sent. The application/x-www-form-urlencoded conent type identifies + * the body as contain form data. If there are duplicates then they + * both are available from the query that is built. + * + * @author Niall Gallagher + */ +class QueryBuilder { + + /** + * This is the request that is used to acquire the data. + */ + private final Request request; + + /** + * This is the header that is used to acquire the data. + */ + private final Header header; + + /** + * Constructor for the <code>QueryBuilder</code> object. This will + * create an object that can be used to construct a single query + * from the multiple sources of data within the request entity. + * + * @param request this is the request to build a query for + * @param entity this is the entity that contains the data + */ + public QueryBuilder(Request request, Entity entity) { + this.header = entity.getHeader(); + this.request = request; + } + + /** + * This method is used to acquire the query part from the HTTP + * request URI target and a form post if it exists. Both the + * query and the form post are merge together in a single query. + * + * @return the query associated with the HTTP target URI + */ + public Query build() {; + Query query = header.getQuery(); + + if(!isFormPost()) { + return query; + } + return getQuery(query); + } + + /** + * This method is used to acquire the query part from the HTTP + * request URI target and a form post if it exists. Both the + * query and the form post are merge together in a single query. + * + * @param query this is the URI query string to be used + * + * @return the query associated with the HTTP target URI + */ + private Query getQuery(Query query) { + String body = getContent(); + + if(body == null) { + return query; + } + return new QueryCombiner(query, body); + } + + /** + * This method attempts to acquire the content of the request + * body. If there is an <code>IOException</code> acquiring the + * content of the body then this will simply return a null + * value without reporting the exception. + * + * @return the content of the body, or null on error + */ + private String getContent() { + try { + return request.getContent(); + } catch(Exception e) { + return null; + } + } + + /** + * This is used to determine if the content type is a form POST + * of type application/x-www-form-urlencoded. Such a type is + * used when a HTML form is used to post data to the server. + * + * @return this returns true if content type is a form post + */ + private boolean isFormPost() { + ContentType type = request.getContentType(); + + if(type == null) { + return false; + } + return isFormPost(type); + } + + /** + * This is used to determine if the content type is a form POST + * of type application/x-www-form-urlencoded. Such a type is + * used when a HTML form is used to post data to the server. + * + * @param type the type to determine if its a form post + * + * @return this returns true if content type is a form post + */ + private boolean isFormPost(ContentType type) { + String primary = type.getPrimary(); + String secondary = type.getSecondary(); + + if(!primary.equals(APPLICATION)) { + return false; + } + return secondary.equals(URL_ENCODED); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/QueryCombiner.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/QueryCombiner.java new file mode 100644 index 0000000..ed4c92e --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/QueryCombiner.java @@ -0,0 +1,148 @@ +/* + * QueryCombiner.java May 2003 + * + * Copyright (C) 2003, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.util.List; +import java.util.Set; + +import org.simpleframework.http.Query; +import org.simpleframework.http.parse.QueryParser; + +/** + * The <code>QueryCombimer</code> is used to parse several strings + * as a complete URL encoded parameter string. This will do the + * following concatenations. + * + * <pre> + * null + "a=b&c=d&e=f" = "a=b&c=d&e=f" + * "a=b" + "e=f&g=h" = "a=b&e=f&g=h"; + * "a=b&c=d&e=f" + "" = "a=b&c=d&e=f" + * </pre> + * + * This ensures that the <code>QueryForm</code> can parse the list + * of strings as a single URL encoded parameter string. This can + * parse any number of parameter strings. + * + * @author Niall Gallagher + */ +class QueryCombiner extends QueryParser { + + /** + * Constructor that allows a list of string objects to be + * parsed as a single parameter string. This will check + * each string to see if it is empty, that is, is either + * null or the zero length string. + * + * @param list this is a list of query values to be used + */ + public QueryCombiner(String... list) { + this.parse(list); + } + + /** + * Constructor that allows an array of string objects to + * be parsed as a single parameter string. This will check + * each string to see if it is empty, that is, is either + * null or the zero length string. + * + * @param query this is the query from the HTTP header + * @param list this is the list of strings to be parsed + */ + public QueryCombiner(Query query, String... list) { + this.add(query); + this.parse(list); + } + + /** + * Constructor that allows an array of string objects to + * be parsed as a single parameter string. This will check + * each string to see if it is empty, that is, is either + * null or the zero length string. + * + * @param query this is the query from the HTTP header + * @param post this is the query from the HTTP post body + */ + public QueryCombiner(Query query, Query post) { + this.add(query); + this.add(post); + } + + /** + * This will concatenate the list of parameter strings as a + * single parameter string, before handing it to be parsed + * by the <code>parse(String)</code> method. This method + * will ignore any null or zero length strings in the array. + * + * @param list this is the list of strings to be parsed + */ + public void parse(String[] list) { + StringBuilder text = new StringBuilder(); + + for(int i = 0; i < list.length; i++) { + if(list[i] == null) { + continue; + } else if(list[i].length()==0){ + continue; + } else if(text.length() > 0){ + text.append("&"); + } + text.append(list[i]); + } + parse(text); + } + + /** + * This is used to perform a parse of the form data that is in + * the provided string builder. This will simply convert the + * data in to a string and parse it in the normal fashion. + * + * @param text this is the buffer to be converted to a string + */ + private void parse(StringBuilder text) { + if(text != null){ + ensureCapacity(text.length()); + count = text.length(); + text.getChars(0, count, buf,0); + parse(); + } + } + + /** + * This method is used to insert a collection of tokens into + * the parsers map. This is used when another source of tokens + * is required to populate the connection currently maintained + * within this parsers internal map. Any tokens that currently + * exist with similar names will be overwritten by this. + * + * @param query this is the collection of tokens to be added + */ + private void add(Query query) { + Set<String> keySet = query.keySet(); + + for(String key : keySet) { + List<String> list = query.getAll(key); + String first = query.get(key); + + if(first != null) { + all.put(key, list); + map.put(key, first); + } + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestCertificate.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestCertificate.java new file mode 100644 index 0000000..1da8b54 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestCertificate.java @@ -0,0 +1,183 @@ +/* + * RequestCertificate.java June 2013 + * + * Copyright (C) 2013, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; +import java.util.concurrent.Future; + +import javax.security.cert.X509Certificate; + +import org.simpleframework.http.message.Entity; +import org.simpleframework.transport.Certificate; +import org.simpleframework.transport.CertificateChallenge; +import org.simpleframework.transport.Channel; + +/** + * The <code>RequestCertificate</code> represents a certificate for + * an HTTP request. It basically wraps the raw SSL certificate that + * comes with the <code>Channel</code>. Wrapping the raw certificate + * allows us to enforce the HTTPS workflow for SSL renegotiation, + * which requires some rather weird behaviour. Most importantly + * we only allow a challenge when the response has not been sent. + * + * @author Niall Gallagher + * + * @see org.simpleframework.transport.CertificateChallenge + */ +class RequestCertificate implements Certificate { + + /** + * This is used to challenge the client for an X509 certificate. + */ + private final CertificateChallenge challenge; + + /** + * This is the raw underlying certificate for the SSL channel. + */ + private final Certificate certificate; + + /** + * This is the channel representing the client connection. + */ + private final Channel channel; + + /** + * Constructor for the <code>RequestCertificate</code>. This is + * used to create a wrapper for the raw SSL certificate that + * is provided by the underlying SSL session. + * + * @param observer the observer used to observe the transaction + * @param entity the request entity containing the data + */ + public RequestCertificate(BodyObserver observer, Entity entity) { + this.challenge = new Challenge(observer, entity); + this.channel = entity.getChannel(); + this.certificate = channel.getCertificate(); + } + + /** + * This will return the X509 certificate chain, if any, that + * has been sent by the client. A certificate chain is typically + * only send when the server explicitly requests the certificate + * on the initial connection or when it is challenged for. + * + * @return this returns the clients X509 certificate chain + */ + public X509Certificate[] getChain() throws Exception { + return certificate.getChain(); + } + + /** + * This returns a challenge for the certificate. A challenge is + * issued by providing a <code>Runnable</code> task which is to + * be executed when the challenge has completed. Typically this + * task should be used to drive completion of an HTTPS request. + * + * @return this returns a challenge for the client certificate + */ + public CertificateChallenge getChallenge() throws Exception { + return challenge; + } + + /** + * This is used to determine if the X509 certificate chain is + * present for the request. If it is not present then a challenge + * can be used to request the certificate. + * + * @return true if the certificate chain is present + */ + public boolean isChainPresent() throws Exception { + return certificate.isChainPresent(); + } + + /** + * The <code>Challenge</code> provides a basic wrapper around the + * challenge provided by the SSL connection. It is used to enforce + * the workflow required by HTTP, this workflow requires that the + * SSL renegotiation be issued before the response is sent. This + * will also throw an exception if a challenge is issued for + * a request that already has a client certificate. + */ + private static class Challenge implements CertificateChallenge { + + /** + * This is the observer used to keep track of the HTTP transaction. + */ + private final BodyObserver observer; + + /** + * This is the certificate associated with the SSL connection. + */ + private final Certificate certificate; + + /** + * This is the channel representing the underlying TCP stream. + */ + private final Channel channel; + + /** + * Constructor for the <code>Challenge</code> object. This is + * basically a wrapper for the raw certificate challenge that + * will enforce some of the workflow required by HTTPS. + * + * @param observer this observer used to track the transaction + * @param entity this entity containing the request data + */ + public Challenge(BodyObserver observer, Entity entity) { + this.channel = entity.getChannel(); + this.certificate = channel.getCertificate(); + this.observer = observer; + } + + /** + * This method will challenge the client for their certificate. + * It does so by performing an SSL renegotiation. Successful + * completion of the SSL renegotiation results in the client + * providing their certificate, and execution of the task. + * + * @param completion task to be run on successful challenge + */ + public Future<Certificate> challenge() throws Exception { + return challenge(null); + } + + /** + * This method will challenge the client for their certificate. + * It does so by performing an SSL renegotiation. Successful + * completion of the SSL renegotiation results in the client + * providing their certificate, and execution of the task. + * + * @param completion task to be run on successful challenge + */ + public Future<Certificate> challenge(Runnable completion) throws Exception { + if(certificate == null) { + throw new IOException("Challenging must be done on a secure connection"); + } + CertificateChallenge challenge = certificate.getChallenge(); + + if(certificate.isChainPresent()) { + throw new IOException("Certificate is already present"); + } + if(observer.isCommitted()) { + throw new IOException("Response has already been committed"); + } + return challenge.challenge(completion); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestCollector.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestCollector.java new file mode 100644 index 0000000..027318d --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestCollector.java @@ -0,0 +1,184 @@ +/* + * RequestCollector.java October 2002 + * + * Copyright (C) 2002, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.simpleframework.http.core.ContainerEvent.REQUEST_READY; +import static org.simpleframework.transport.TransportEvent.READ_WAIT; + +import java.io.IOException; +import java.nio.channels.SocketChannel; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.http.message.Body; +import org.simpleframework.http.message.EntityConsumer; +import org.simpleframework.http.message.Header; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteCursor; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>RequestCollector</code> object is used to collect all of + * the data used to form a request entity. This will collect the data + * fragment by fragment from the underlying transport. When all of + * the data is consumed and the entity is created and then it is sent + * to the <code>Selector</code> object for processing. If the request + * has completed the next request can be collected from the + * underlying transport using a new collector object. + * + * @author Niall Gallagher + */ +class RequestCollector implements Collector { + + /** + * This is used to consume the request entity from the channel. + */ + private final EntityConsumer entity; + + /** + * This is the cursor used to read and reset the data. + */ + private final ByteCursor cursor; + + /** + * This is the channel used to acquire the underlying data. + */ + private final Channel channel; + + /** + * This is the trace used to listen for various collect events. + */ + private final Trace trace; + + /** + * This represents the time the request collection began at. + */ + private final Timer timer; + + /** + * The <code>RequestCollector</code> object used to collect the data + * from the underlying transport. In order to collect a body this + * must be given an <code>Allocator</code> which is used to create + * an internal buffer to store the consumed body. + * + * @param allocator this is the allocator used to buffer data + * @param tracker this is the tracker used to create sessions + * @param channel this is the channel used to read the data + */ + public RequestCollector(Allocator allocator, Channel channel) { + this.entity = new EntityConsumer(allocator, channel); + this.timer = new Timer(MILLISECONDS); + this.cursor = channel.getCursor(); + this.trace = channel.getTrace(); + this.channel = channel; + } + + /** + * This is used to collect the data from a <code>Channel</code> + * which is used to compose the entity. If at any stage there + * are no ready bytes on the socket the controller provided can + * be used to queue the collector until such time as the socket + * is ready to read. Also, should the entity have completed reading + * all required content it is handed to the controller as ready, + * which processes the entity as a new client HTTP request. + * + * @param controller this is the controller used to queue this + */ + public void collect(Controller controller) throws IOException { + while(cursor.isReady()) { + if(entity.isFinished()) { + break; + } else { + timer.set(); + entity.consume(cursor); + } + } + if(cursor.isOpen()) { + if(entity.isFinished()) { + trace.trace(REQUEST_READY); + controller.ready(this); + } else { + trace.trace(READ_WAIT); + controller.select(this); + } + } + } + + /** + * This is the time in milliseconds when the request was first + * read from the underlying channel. The time represented here + * represents the time collection of this request began. This + * does not necessarily represent the time the bytes arrived on + * the receive buffers as some data may have been buffered. + * + * @return this represents the time the request was ready at + */ + public long getTime() { + return timer.get(); + } + + /** + * This provides the HTTP request header for the entity. This is + * always populated and provides the details sent by the client + * such as the target URI and the query if specified. Also this + * can be used to determine the method and protocol version used. + * + * @return the header provided by the HTTP request message + */ + public Header getHeader() { + return entity.getHeader(); + } + + /** + * This is used to acquire the body for this HTTP entity. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Part</code> objects. Each + * part can then be read as an individual message. + * + * @return the body provided by the HTTP request message + */ + public Body getBody() { + return entity.getBody(); + } + + /** + * This provides the connected channel for the client. This is + * used to send and receive bytes to and from an transport layer. + * Each channel provided with an entity contains an attribute + * map which contains information about the connection. + * + * @return the connected channel for this HTTP entity + */ + public Channel getChannel() { + return channel; + } + + /** + * This returns the socket channel that is used by the collector + * to read content from. This is a selectable socket, in that + * it can be registered with a Java NIO selector. This ensures + * that the system can be notified when the socket is ready. + * + * @return the socket channel used by this collector object + */ + public SocketChannel getSocket() { + return channel.getSocket(); + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestDispatcher.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestDispatcher.java new file mode 100644 index 0000000..6b1531a --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestDispatcher.java @@ -0,0 +1,128 @@ +/* + * RequestDispatcher.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static org.simpleframework.http.core.ContainerEvent.DISPATCH_FINISHED; +import static org.simpleframework.http.core.ContainerEvent.DISPATCH_REQUEST; +import static org.simpleframework.http.core.ContainerEvent.ERROR; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.http.message.Entity; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>RequestDispatcher</code> object is used to dispatch a + * request and response to the container. This is the root task that + * executes all transactions. A transaction is dispatched to the + * container which can deal with it asynchronously, however as a + * safeguard the dispatcher will catch any exceptions thrown and close + * the connection if required. Closing the connection if an exception + * is thrown ensures that CLOSE_WAIT issues do not arise with open + * connections that can not be closed within the container. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.core.Container + */ +class RequestDispatcher implements Runnable { + + /** + * This is the observer object used to signal completion events. + */ + private final ResponseObserver observer; + + /** + * This is the container that is used to handle the transactions. + */ + private final Container container; + + /** + * This is the response object used to response to the request. + */ + private final Response response; + + /** + * This is the request object which contains the request entity. + */ + private final Request request; + + /** + * This is the channel associated with the request to dispatch. + */ + private final Channel channel; + + /** + * This is the trace that is used to track the request dispatch. + */ + private final Trace trace; + + /** + * Constructor for the <code>RequestDispatcher</code> object. This + * creates a request and response object using the provided entity, + * these can then be passed to the container to handle it. + * + * @param container this is the container to handle the request + * @param controller the controller used to handle the next request + * @param entity this contains the current request entity + */ + public RequestDispatcher(Container container, Controller controller, Entity entity) { + this.observer = new ResponseObserver(controller, entity); + this.request = new RequestEntity(observer, entity); + this.response = new ResponseEntity(observer, request, entity); + this.channel = entity.getChannel(); + this.trace = channel.getTrace(); + this.container = container; + } + + /** + * This <code>run</code> method will dispatch the created request + * and response objects to the container. This will interpret the + * target and semantics from the request object and compose a + * response for the request which is sent to the connected client. + */ + public void run() { + try { + dispatch(); + } catch(Exception cause) { + trace.trace(ERROR, cause); + } finally { + trace.trace(DISPATCH_FINISHED); + } + } + + /** + * This <code>dispatch</code> method will dispatch the request + * and response objects to the container. This will interpret the + * target and semantics from the request object and compose a + * response for the request which is sent to the connected client. + * If there is an exception this will close the socket channel. + */ + private void dispatch() throws Exception { + try { + trace.trace(DISPATCH_REQUEST); + container.handle(request, response); + } catch(Throwable cause) { + trace.trace(ERROR, cause); + channel.close(); + } + } +} + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestEntity.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestEntity.java new file mode 100644 index 0000000..6060a4f --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestEntity.java @@ -0,0 +1,398 @@ +/* + * RequestEntity.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static org.simpleframework.http.Protocol.CLOSE; +import static org.simpleframework.http.Protocol.CONNECTION; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SocketChannel; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.simpleframework.http.ContentType; +import org.simpleframework.http.Part; +import org.simpleframework.http.Query; +import org.simpleframework.http.Request; +import org.simpleframework.http.message.Body; +import org.simpleframework.http.message.Entity; +import org.simpleframework.transport.Certificate; +import org.simpleframework.transport.Channel; + +/** + * This object is used to represent a HTTP request. This defines the + * attributes that a HTTP request has such as a request line and the + * headers that come with the message header. + * <p> + * The <code>Request</code> is used to provide an interface to the + * HTTP <code>InputStream</code> and message header. The stream can + * have certain characteristics, these characteristics are available + * by this object. The <code>Request</code> provides methods that + * allow the <code>InputStream</code>'s semantics to be known, for + * example if the stream is keep-alive or if the stream has a length. + * <p> + * The <code>Request</code> origin is also retrievable from the + * <code>Request</code> as is the attributes <code>Map</code> object + * which defines specific connection attributes. And acts as a + * simple model for the request transaction. + * <p> + * It is important to note that the <code>Request</code> controls + * the processing of the HTTP pipeline. The next HTTP request is + * not processed until the request has read all of the content body + * within the <code>InputStream</code>. The stream must be fully + * read or closed for the next request to be processed. + * + * @author Niall Gallagher + */ +class RequestEntity extends RequestMessage implements Request { + + /** + * This is the certificate associated with the connection. + */ + private Certificate certificate; + + /** + * This will create the form object using the query and body. + */ + private QueryBuilder builder; + + /** + * This channel represents the connected pipeline used. + */ + private Channel channel; + + /** + * The query contains all the parameters for the request. + */ + private Query query; + + /** + * The body contains the message content sent by the client. + */ + private Body body; + + /** + * This is used to contain the values for this request. + */ + private Map map; + + /** + * This is the time at which the request is ready to be used. + */ + private long time; + + /** + * Constructor for the <code>RequestEntity</code> object. This is + * used to create a request that contains all the parts sent by + * the client, including the headers and the request body. Each of + * the request elements are accessible through this object in a + * convenient manner, all parts and parameters, as well as cookies + * can be accessed and used without much effort. + * + * @param observer this is the observer used to monitor events + * @param entity this is the entity that was sent by the client + */ + public RequestEntity(ResponseObserver observer, Entity entity) { + this.certificate = new RequestCertificate(observer, entity); + this.builder = new QueryBuilder(this, entity); + this.channel = entity.getChannel(); + this.header = entity.getHeader(); + this.body = entity.getBody(); + this.time = entity.getTime(); + } + + /** + * This is used to determine if the request has been transferred + * over a secure connection. If the protocol is HTTPS and the + * content is delivered over SSL then the request is considered + * to be secure. Also the associated response will be secure. + * + * @return true if the request is transferred securely + */ + public boolean isSecure() { + return channel.isSecure(); + } + + /** + * This is a convenience method that is used to determine whether + * or not this message has the Connection header with the close + * token. If the close token is present then this stream is not a + * keep-alive connection. However if this has no Connection header + * then the keep alive status is determined by the HTTP version, + * that is HTTP/1.1 is keep alive by default, HTTP/1.0 has the + * connection close by default. + * + * @return returns true if this is keep alive connection + */ + public boolean isKeepAlive(){ + String value = getValue(CONNECTION); + + if(value == null) { + int major = getMajor(); + int minor = getMinor(); + + if(major > 1) { + return true; + } + if(major == 1) { + return minor > 0; + } + return false; + } + return !value.equalsIgnoreCase(CLOSE); + } + + /** + * This is the time in milliseconds when the request was first + * read from the underlying socket. The time represented here + * represents the time collection of this request began. This + * does not necessarily represent the time the bytes arrived on + * the receive buffers as some data may have been buffered. + * + * @return this represents the time the request arrived at + */ + public long getRequestTime() { + return time; + } + + /** + * This provides the underlying channel for the request. It + * contains the TCP socket channel and various other low level + * components. Typically this will only ever be needed when + * there is a need to switch protocols. + * + * @return the underlying channel for this request + */ + public Channel getChannel() { + return channel; + } + + /** + * This is used to acquire the SSL certificate used when the + * server is using a HTTPS connection. For plain text connections + * or connections that use a security mechanism other than SSL + * this will be null. This is only available when the connection + * makes specific use of an SSL engine to secure the connection. + * + * @return this returns the associated SSL certificate if any + */ + public Certificate getClientCertificate() { + if(channel.isSecure()) { + return certificate; + } + return null; + } + + /** + * This is used to acquire the remote client address. This can + * be used to acquire both the port and the I.P address for the + * client. It allows the connected clients to be logged and if + * require it can be used to perform course grained security. + * + * @return this returns the client address for this request + */ + public InetSocketAddress getClientAddress() { + SocketChannel socket = channel.getSocket(); + Socket client = socket.socket(); + + return getClientAddress(client); + } + + /** + * This is used to acquire the remote client address. This can + * be used to acquire both the port and the I.P address for the + * client. It allows the connected clients to be logged and if + * require it can be used to perform course grained security. + * + * @param socket this is the socket to get the address for + * + * @return this returns the client address for this request + */ + private InetSocketAddress getClientAddress(Socket socket) { + InetAddress address = socket.getInetAddress(); + int port = socket.getPort(); + + return new InetSocketAddress(address, port); + } + + /** + * This is used to get the content body. This will essentially get + * the content from the body and present it as a single string. + * The encoding of the string is determined from the content type + * charset value. If the charset is not supported this will throw + * an exception. Typically only text values should be extracted + * using this method if there is a need to parse that content. + * + * @return the body content containing the message body + */ + public String getContent() throws IOException { + ContentType type = getContentType(); + + if(type == null) { + return body.getContent("ISO-8859-1"); + } + return getContent(type); + } + + /** + * This is used to get the content body. This will essentially get + * the content from the body and present it as a single string. + * The encoding of the string is determined from the content type + * charset value. If the charset is not supported this will throw + * an exception. Typically only text values should be extracted + * using this method if there is a need to parse that content. + * + * @param type this is the content type used with the request + * + * @return the input stream containing the message body + */ + public String getContent(ContentType type) throws IOException { + String charset = type.getCharset(); + + if(charset == null) { + charset = "ISO-8859-1"; + } + return body.getContent(charset); + } + + /** + * This is used to read the content body. The specifics of the data + * that is read from this <code>InputStream</code> can be determined + * by the <code>getContentLength</code> method. If the data sent by + * the client is chunked then it is decoded, see RFC 2616 section + * 3.6. Also multipart data is available as <code>Part</code> objects + * however the raw content of the multipart body is still available. + * + * @return the input stream containing the message body + */ + public InputStream getInputStream() throws IOException { + return body.getInputStream(); + } + + /** + * This is used to read the content body. The specifics of the data + * that is read from this <code>ReadableByteChannel</code> can be + * determined by the <code>getContentLength</code> method. If the + * data sent by the client is chunked then it is decoded, see RFC + * 2616 section 3.6. This stream will never provide empty reads as + * the content is internally buffered, so this can do a full read. + * + * @return this returns the byte channel used to read the content + */ + public ReadableByteChannel getByteChannel() throws IOException { + InputStream source = getInputStream(); + + if(source != null) { + return Channels.newChannel(source); + } + return null; + } + + /** + * This can be used to retrieve the response attributes. These can + * be used to keep state with the response when it is passed to + * other systems for processing. Attributes act as a convenient + * model for storing objects associated with the response. This + * also inherits attributes associated with the client connection. + * + * @return the attributes that have been added to this request + */ + public Map getAttributes() { + Map common = channel.getAttributes(); + + if(map == null) { + map = new HashMap(common); + } + return map; + } + + /** + * This is used as a shortcut for acquiring attributes for the + * response. This avoids acquiring the attribute <code>Map</code> + * in order to retrieve the attribute directly from that object. + * The attributes contain data specific to the response. + * + * @param key this is the key of the attribute to acquire + * + * @return this returns the attribute for the specified name + */ + public Object getAttribute(Object key) { + return getAttributes().get(key); + } + + /** + * This method is used to acquire the query part from the HTTP + * request URI target and a form post if it exists. Both the + * query and the form post are merge together in a single query. + * + * @return the query associated with the HTTP target URI + */ + public Query getQuery() { + if(query == null) { + query = builder.build(); + } + return query; + } + + /** + * This is used to provide quick access to the parameters. This + * avoids having to acquire the request <code>Form</code> object. + * This basically acquires the parameters object and invokes + * the <code>getParameters</code> method with the given name. + * + * @param name this is the name of the parameter value + */ + public String getParameter(String name) { + return getQuery().get(name); + } + + /** + * This method is used to acquire a <code>Part</code> from the + * HTTP request using a known name for the part. This is typically + * used when there is a file upload with a multipart POST request. + * All parts that are not files can be acquired as string values + * from the attachment object. + * + * @param name this is the name of the part object to acquire + * + * @return the named part or null if the part does not exist + */ + public Part getPart(String name) { + return body.getPart(name); + } + + /** + * This method is used to get all <code>Part</code> objects that + * are associated with the request. Each attachment contains the + * body and headers associated with it. If the request is not a + * multipart POST request then this will return an empty list. + * + * @return the list of parts associated with this request + */ + public List<Part> getParts() { + return body.getParts(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestMessage.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestMessage.java new file mode 100644 index 0000000..b22c74e --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestMessage.java @@ -0,0 +1,341 @@ +/* + * RequestMessage.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.util.List; +import java.util.Locale; + +import org.simpleframework.http.Address; +import org.simpleframework.http.ContentType; +import org.simpleframework.http.Cookie; +import org.simpleframework.http.Path; +import org.simpleframework.http.Query; +import org.simpleframework.http.RequestHeader; +import org.simpleframework.http.message.Header; + +/** + * The <code>RequestMessage</code> object is used to create a HTTP + * request header representation. All requests for details within a + * request message delegates to an underlying header, which contains + * all of the header names and values sent by the client. The header + * names are case insensitively mapped as required by RFC 2616. + * + * @author Niall Gallagher + */ +class RequestMessage implements RequestHeader { + + /** + * This is the underlying header used to house the headers. + */ + protected Header header; + + /** + * Constructor for the <code>RequestMessage</code> object. This + * is used to create a request message without an underlying + * header. In such an event it is up to the subclass to provide + * the instance, this is useful for testing the request. + */ + public RequestMessage() { + super(); + } + + /** + * Constructor for the <code>RequestMessage</code> object. This + * is used to create a request with a header instance. In such + * a case the header provided will be queried for headers and is + * used to store headers added to this message instance. + * + * @param header this is the backing header for the message + */ + public RequestMessage(Header header) { + this.header = header; + } + + /** + * This can be used to get the URI specified for this HTTP + * request. This corresponds to the /index part of a + * http://www.domain.com/index URL but may contain the full + * URL. This is a read only value for the request. + * + * @return the URI that this HTTP request is targeting + */ + public String getTarget() { + return header.getTarget(); + } + + /** + * This is used to acquire the address from the request line. + * An address is the full URI including the scheme, domain, port + * and the query parts. This allows various parameters to be + * acquired without having to parse the raw request target URI. + * + * @return this returns the address of the request line + */ + public Address getAddress() { + return header.getAddress(); + } + + /** + * This is used to acquire the path as extracted from the HTTP + * request URI. The <code>Path</code> object that is provided by + * this method is immutable, it represents the normalized path + * only part from the request uniform resource identifier. + * + * @return this returns the normalized path for the request + */ + public Path getPath() { + return header.getPath(); + } + + /** + * This method is used to acquire the query part from the + * HTTP request URI target. This will return only the values + * that have been extracted from the request URI target. + * + * @return the query associated with the HTTP target URI + */ + public Query getQuery() { + return header.getQuery(); + } + + /** + * This can be used to get the HTTP method for this request. The + * HTTP specification RFC 2616 specifies the HTTP request methods + * in section 9, Method Definitions. Typically this will be a + * GET, POST or a HEAD method, although any string is possible. + * + * @return the request method for this request message + */ + public String getMethod() { + return header.getMethod(); + } + + /** + * This can be used to get the major number from a HTTP version. + * The major version corresponds to the major type that is the 1 + * of a HTTP/1.0 version string. + * + * @return the major version number for the request message + */ + public int getMajor() { + return header.getMajor(); + } + + /** + * This can be used to get the major number from a HTTP version. + * The major version corresponds to the major type that is the 0 + * of a HTTP/1.0 version string. This is used to determine if + * the request message has keep alive semantics. + * + * @return the major version number for the request message + */ + public int getMinor() { + return header.getMinor(); + } + + /** + * This method is used to get a <code>List</code> of the names + * for the headers. This will provide the original names for the + * HTTP headers for the message. Modifications to the provided + * list will not affect the header, the list is a simple copy. + * + * @return this returns a list of the names within the header + */ + public List<String> getNames() { + return header.getNames(); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma seperated list of + * values the value returned is the first value in that list. + * This returns null if theres no HTTP message header. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name) { + return header.getValue(name); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma separated list of + * values the value returned is the first value in that list. + * This returns null if theres no HTTP message header. + * + * @param name the HTTP message header to get the value from + * @param index if there are multiple values this selects one + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name, int index) { + return header.getValue(name, index); + } + + /** + * This can be used to get the integer of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + public int getInteger(String name) { + return header.getInteger(name); + } + + /** + * This can be used to get the date of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + public long getDate(String name) { + return header.getDate(name); + } + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benifits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearence. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has higest preference. + * + * @param name the name of the headers that are to be retrieved + * + * @return ordered array of tokens extracted from the header(s) + */ + public List<String> getValues(String name) { + return header.getValues(name); + } + + /** + * This is used to acquire the locales from the request header. The + * locales are provided in the <code>Accept-Language</code> header. + * This provides an indication as to the languages that the client + * accepts. It provides the locales in preference order. + * + * @return this returns the locales preferred by the client + */ + public List<Locale> getLocales() { + return header.getLocales(); + } + + /** + * This is used to acquire a cookie usiing the name of that cookie. + * If the cookie exists within the HTTP header then it is returned + * as a <code>Cookie</code> object. Otherwise this method will + * return null. Each cookie object will contain the name, value + * and path of the cookie as well as the optional domain part. + * + * @param name this is the name of the cookie object to acquire + * + * @return this returns a cookie object from the header or null + */ + public Cookie getCookie(String name) { + return header.getCookie(name); + } + + /** + * This is used to acquire all cookies that were sent in the header. + * If any cookies exists within the HTTP header they are returned + * as <code>Cookie</code> objects. Otherwise this method will an + * empty list. Each cookie object will contain the name, value and + * path of the cookie as well as the optional domain part. + * + * @return this returns all cookie objects from the HTTP header + */ + public List<Cookie> getCookies() { + return header.getCookies(); + } + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Type</code> header, if there is then + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content type value if it exists + */ + public ContentType getContentType() { + return header.getContentType(); + } + + /** + * This is a convenience method that can be used to determine + * the length of the message body. This will determine if there + * is a <code>Content-Length</code> header, if it does then the + * length can be determined, if not then this returns -1. + * + * @return the content length, or -1 if it cannot be determined + */ + public long getContentLength() { + return header.getContentLength(); + } + + /** + * This method returns a <code>CharSequence</code> holding the header + * consumed for the request. A character sequence is returned as it + * can provide a much more efficient means of representing the header + * data by just wrapping the consumed byte array. + * + * @return this returns the characters consumed for the header + */ + public CharSequence getHeader() { + return header.getHeader(); + } + + /** + * This is used to provide a string representation of the header + * read. Providing a string representation of the header is used + * so that on debugging the contents of the delivered header can + * be inspected in order to determine a cause of error. + * + * @return this returns a string representation of the header + */ + public String toString() { + return header.toString(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestReader.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestReader.java new file mode 100644 index 0000000..6f8cbaa --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/RequestReader.java @@ -0,0 +1,131 @@ +/* + * RequestReader.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static org.simpleframework.http.core.ContainerEvent.ERROR; + +import java.nio.channels.SocketChannel; + +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.reactor.Operation; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>RequestReader</code> object is used to read the bytes + * that form the request entity. In order to execute a read operation + * the socket must be read ready. This is determined using the socket + * object, which is registered with a controller. If at any point the + * reading results in an error the operation is cancelled and the + * collector is closed, which shuts down the connection. + * + * @author Niall Gallagher + * + * @see org.simpleframework.transport.reactor.Reactor + */ +class RequestReader implements Operation { + + /** + * This is the selector used to process the collection events. + */ + private final Controller controller; + + /** + * This is the collector used to consume the entity bytes. + */ + private final Collector collector; + + /** + * This is the channel object associated with the collector. + */ + private final Channel channel; + + /** + * This is used to collect any trace information. + */ + private final Trace trace; + + /** + * Constructor for the <code>RequestReader</code> object. This is + * used to collect the data required to compose a HTTP request. + * Once all the data has been read by this it is dispatched. + * + * @param controller the controller object used to process events + * @param collector this is the task used to collect the entity + */ + public RequestReader(Controller controller, Collector collector){ + this.channel = collector.getChannel(); + this.trace = channel.getTrace(); + this.collector = collector; + this.controller = controller; + } + + /** + * This is used to acquire the trace object that is associated + * with the operation. A trace object is used to collection details + * on what operations are being performed. For instance it may + * contain information relating to I/O events or errors. + * + * @return this returns the trace associated with this operation + */ + public Trace getTrace() { + return trace; + } + + /** + * This is the <code>SocketChannel</code> used to determine if the + * connection has some bytes that can be read. If it contains any + * data then that data is read from and is used to compose the + * request entity, which consists of a HTTP header and body. + * + * @return this returns the socket for the connected pipeline + */ + public SocketChannel getChannel() { + return channel.getSocket(); + } + + /** + * This <code>run</code> method is used to collect the bytes from + * the connected channel. If a sufficient amount of data is read + * from the socket to form a HTTP entity then the collector uses + * the <code>Selector</code> object to dispatch the request. This + * is sequence of events that occur for each transaction. + */ + public void run() { + try { + collector.collect(controller); + }catch(Throwable cause){ + trace.trace(ERROR, cause); + channel.close(); + } + } + + /** + * This is used to cancel the operation if it has timed out. If + * the retry is waiting too long to read content from the socket + * then the retry is cancelled and the underlying transport is + * closed. This helps to clean up occupied resources. + */ + public void cancel() { + try { + channel.close(); + } catch(Throwable cause) { + trace.trace(ERROR, cause); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseBuffer.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseBuffer.java new file mode 100644 index 0000000..d2a9f80 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseBuffer.java @@ -0,0 +1,303 @@ +/* + * ResponseBuffer.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; + +import org.simpleframework.http.Response; +import org.simpleframework.http.message.Entity; +import org.simpleframework.transport.Channel; + +/** + * The <code>ResponseBuffer</code> object is an output stream that can + * buffer bytes written up to a given size. This is used if a buffer + * is requested for the response output. Such a mechanism allows the + * response to be written without committing the response. Also it + * enables content that has been written to be reset, by simply + * clearing the response buffer. If the response buffer overflows + * then the response is committed. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.core.ResponseEncoder + */ +class ResponseBuffer extends OutputStream implements WritableByteChannel { + + /** + * This is the transfer object used to transfer the response. + */ + private ResponseEncoder encoder; + + /** + * This is the buffer used to accumulate the response bytes. + */ + private byte[] buffer; + + /** + * This is used to determine if the accumulate was flushed. + */ + private boolean flushed; + + /** + * This is used to determine if the accumulator was closed. + */ + private boolean closed; + + /** + * This counts the number of bytes that have been accumulated. + */ + private int count; + + /** + * Constructor for the <code>ResponseBuffer</code> object. This will + * create a buffering output stream which will flush data to the + * underlying transport provided with the entity. All I/O events + * are reported to the monitor so the server can process other + * requests within the pipeline when the current one is finished. + * + * @param observer this is used to notify of response completion + * @param response this is the response header for this buffer + * @param support this is used to determine the response semantics + * @param entity this is used to acquire the underlying transport + */ + public ResponseBuffer(BodyObserver observer, Response response, Conversation support, Entity entity) { + this(observer, response, support, entity.getChannel()); + } + + /** + * Constructor for the <code>ResponseBuffer</code> object. This will + * create a buffering output stream which will flush data to the + * underlying transport provided with the channel. All I/O events + * are reported to the monitor so the server can process other + * requests within the pipeline when the current one is finished. + * + * @param observer this is used to notify of response completion + * @param response this is the response header for this buffer + * @param support this is used to determine the response semantics + * @param channel this is the channel used to write the data to + */ + public ResponseBuffer(BodyObserver observer, Response response, Conversation support, Channel channel) { + this.encoder = new ResponseEncoder(observer, response, support, channel); + this.buffer = new byte[] {}; + } + + /** + * This is used to determine if the accumulator is still open. If + * the accumulator is still open then data can still be written to + * it and this transmitted to the client. When the accumulator is + * closed the data is committed and this can not be used. + * + * @return this returns true if the accumulator object is open + */ + public boolean isOpen() { + return !closed; + } + + /** + * This is used to reset the buffer so that it can be written to + * again. If the accumulator has already been flushed then the + * stream can not be reset. Resetting the stream is typically + * done if there is an error in writing the response and an error + * message is generated to replaced the partial response. + */ + public void reset() throws IOException { + if(flushed) { + throw new IOException("Response has been flushed"); + } + count = 0; + } + + /** + * This is used to write the provided octet to the buffer. If the + * buffer is full it will be flushed and the octet is appended to + * the start of the buffer. If however the buffer is zero length + * then this will write directly to the underlying transport. + * + * @param octet this is the octet that is to be written + */ + public void write(int octet) throws IOException { + byte value = (byte) octet; + + if(closed) { + throw new IOException("Response has been transferred"); + } + write(new byte[] { value }); + } + + /** + * This is used to write the provided array to the buffer. If the + * buffer is full it will be flushed and the array is appended to + * the start of the buffer. If however the buffer is zero length + * then this will write directly to the underlying transport. + * + * @param array this is the array of bytes to send to the client + * @param off this is the offset within the array to send from + * @param size this is the number of bytes that are to be sent + */ + public void write(byte[] array, int off, int size) throws IOException { + ByteBuffer buffer = ByteBuffer.wrap(array, off, size); + + if(size > 0) { + write(buffer); + } + } + + /** + * This is used to write the provided buffer to the buffer. If the + * buffer is full it will be flushed and the buffer is appended to + * the start of the buffer. If however the buffer is zero length + * then this will write directly to the underlying transport. + * + * @param source this is the byte buffer to send to the client + * + * @return this returns the number of bytes that have been sent + */ + public int write(ByteBuffer source) throws IOException { + int mark = source.position(); + int size = source.limit(); + + if(mark > size) { + throw new ResponseException("Buffer position greater than limit"); + } + return write(source, 0, size - mark); + } + + /** + * This is used to write the provided buffer to the buffer. If the + * buffer is full it will be flushed and the buffer is appended to + * the start of the buffer. If however the buffer is zero length + * then this will write directly to the underlying transport. + * + * @param source this is the byte buffer to send to the client + * @param off this is the offset within the array to send from + * @param size this is the number of bytes that are to be sent + * + * @return this returns the number of bytes that have been sent + */ + public int write(ByteBuffer source, int off, int size) throws IOException { + if(closed) { + throw new IOException("Response has been transferred"); + } + int mark = source.position(); + int limit = source.limit(); + + if(limit - mark < size) { // not enough data + size = limit - mark; // reduce expectation + } + if(count + size > buffer.length) { + flush(false); + } + if(size > buffer.length){ + encoder.write(source); + } else { + source.get(buffer, count, size); + count += size; + } + return size; + } + + /** + * This is used to expand the capacity of the internal buffer. If + * there is already content that has been appended to the buffer + * this will copy that data to the newly created buffer. This + * will not decrease the size of the buffer if it is larger than + * the requested capacity. + * + * @param capacity this is the capacity to expand the buffer to + */ + public void expand(int capacity) throws IOException { + if(buffer.length < capacity) { + int size = buffer.length * 2; + int resize = Math.max(capacity, size); + byte[] temp = new byte[resize]; + + System.arraycopy(buffer, 0, temp, 0, count); + buffer = temp; + } + } + + /** + * This is used to flush the contents of the buffer to the + * underlying transport. Once the accumulator is flushed the HTTP + * headers are written such that the semantics of the connection + * match the protocol version and the existing response headers. + */ + public void flush() throws IOException { + flush(true); + } + + /** + * This is used to flush the contents of the buffer to the + * underlying transport. Once the accumulator is flushed the HTTP + * headers are written such that the semantics of the connection + * match the protocol version and the existing response headers. + * + * @param flush indicates whether the transport should be flushed + */ + private void flush(boolean flush) throws IOException { + if(!flushed) { + encoder.start(); + } + if(count > 0) { + encoder.write(buffer, 0, count); + } + if(flush) { + encoder.flush(); + } + flushed = true; + count = 0; + } + + /** + * This will flush the buffer to the underlying transport and + * close the stream. Once the accumulator is flushed the HTTP + * headers are written such that the semantics of the connection + * match the protocol version and the existing response headers. + * Closing this stream does not mean the connection is closed. + */ + public void close() throws IOException { + if(!closed) { + commit(); + } + flushed = true; + closed = true; + } + + /** + * This will close the underlying transfer object which will + * notify the server kernel that the next request is read to be + * processed. If the accumulator is unflushed then this will set + * a Content-Length header such that it matches the number of + * bytes that are buffered within the internal buffer. + */ + private void commit() throws IOException { + if(!flushed) { + encoder.start(count); + } + if(count > 0) { + encoder.write(buffer, 0, count); + } + encoder.close(); + } +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseEncoder.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseEncoder.java new file mode 100644 index 0000000..0fbe39b --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseEncoder.java @@ -0,0 +1,324 @@ +/* + * ResponseEncoder.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static org.simpleframework.http.core.ContainerEvent.WRITE_BODY; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.simpleframework.http.Response; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>ResponseEncoder</code> object acts as a means to determine + * the transfer encoding for the response body. This will ensure that + * the correct HTTP headers are used when the transfer of the body begins. + * In order to determine what headers to use this can be provided + * with a content length value. If the <code>start</code> method is + * provided with the content length then the HTTP headers will use a + * Content-Length header as the message delimiter. If there is no + * content length provided then the chunked encoding is used for + * HTTP/1.1 and connection close is used for HTTP/1.0. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.core.BodyEncoder + */ +class ResponseEncoder { + + /** + * This is used to create a encoder based on the HTTP headers. + */ + private BodyEncoderFactory factory; + + /** + * This is used to determine the type of transfer required. + */ + private Conversation support; + + /** + * This is the response message that is to be committed. + */ + private Response response; + + /** + * Once the header is committed this is used to produce data. + */ + private BodyEncoder encoder; + + /** + * This is the trace used to monitor events in the data transfer. + */ + private Trace trace; + + /** + * Constructor for the <code>ResponseEncoder</code> object, this is + * used to create an object used to transfer a response body. This + * must be given a <code>Conversation</code> that can be used to set + * and get information regarding the type of transfer required. + * + * @param observer this is used to signal for response completion + * @param response this is the actual response message + * @param support this is used to determine the semantics + * @param channel this is the connected TCP channel for the response + */ + public ResponseEncoder(BodyObserver observer, Response response, Conversation support, Channel channel) { + this.factory = new BodyEncoderFactory(observer, support, channel); + this.trace = channel.getTrace(); + this.response = response; + this.support = support; + } + + /** + * This is used to determine if the transfer has started. It has + * started when a encoder is created and the HTTP headers have + * been sent, or at least handed to the underlying transport. + * Once started the semantics of the connection can not change. + * + * @return this returns whether the transfer has started + */ + public boolean isStarted() { + return encoder != null; + } + + /** + * This starts the transfer with no specific content length set. + * This is typically used when dynamic data is emitted ans will + * require chunked encoding for HTTP/1.1 and connection close + * for HTTP/1.0. Once invoked the HTTP headers are committed. + */ + public void start() throws IOException { + if(encoder != null) { + throw new ResponseException("Transfer has already started"); + } + clear(); + configure(); + commit(); + } + + /** + * This starts the transfer with a known content length. This is + * used when there is a Content-Length header set. This will not + * encode the content for HTTP/1.1 however, HTTP/1.0 may need + * a connection close if it does not have keep alive semantics. + * + * @param length this is the length of the response body + */ + public void start(int length) throws IOException { + if(encoder != null) { + throw new ResponseException("Transfer has already started"); + } + clear(); + configure(length); + commit(); + } + + /** + * This method is used to write content to the underlying socket. + * This will make use of the <code>Producer</code> object to + * encode the response body as required. If the encoder has not + * been created then this will throw an exception. + * + * @param array this is the array of bytes to send to the client + */ + public void write(byte[] array) throws IOException { + write(array, 0, array.length); + } + + /** + * This method is used to write content to the underlying socket. + * This will make use of the <code>Producer</code> object to + * encode the response body as required. If the encoder has not + * been created then this will throw an exception. + * + * @param array this is the array of bytes to send to the client + * @param off this is the offset within the array to send from + * @param len this is the number of bytes that are to be sent + */ + public void write(byte[] array, int off, int len) throws IOException { + if(encoder == null) { + throw new ResponseException("Conversation details not ready"); + } + trace.trace(WRITE_BODY, len); + encoder.encode(array, off, len); + } + + /** + * This method is used to write content to the underlying socket. + * This will make use of the <code>Producer</code> object to + * encode the response body as required. If the encoder has not + * been created then this will throw an exception. + * + * @param buffer this is the buffer of bytes to send to the client + */ + public void write(ByteBuffer buffer) throws IOException { + int mark = buffer.position(); + int size = buffer.limit(); + + if(mark > size) { + throw new ResponseException("Buffer position greater than limit"); + } + write(buffer, 0, size - mark); + } + + /** + * This method is used to write content to the underlying socket. + * This will make use of the <code>Producer</code> object to + * encode the response body as required. If the encoder has not + * been created then this will throw an exception. + * + * @param buffer this is the buffer of bytes to send to the client + * @param off this is the offset within the buffer to send from + * @param len this is the number of bytes that are to be sent + */ + public void write(ByteBuffer buffer, int off, int len) throws IOException { + if(encoder == null) { + throw new ResponseException("Conversation details not ready"); + } + trace.trace(WRITE_BODY, len); + encoder.encode(buffer, off, len); + } + + /** + * This method is used to flush the contents of the buffer to + * the client. This method will block until such time as all of + * the data has been sent to the client. If at any point there + * is an error sending the content an exception is thrown. + */ + public void flush() throws IOException { + if(encoder == null) { + throw new ResponseException("Conversation details not ready"); + } + encoder.flush(); + } + + /** + * This is used to signal to the encoder that all content has + * been written and the user no longer needs to write. This will + * either close the underlying transport or it will notify the + * monitor that the response has completed and the next request + * can begin. This ensures the content is flushed to the client. + */ + public void close() throws IOException { + if(encoder == null) { + throw new ResponseException("Conversation details not ready"); + } + encoder.close(); + } + + /** + * This method is used to set the required HTTP headers on the + * response. This will check the existing HTTP headers, and if + * there is insufficient data chunked encoding will be used for + * HTTP/1.1 and connection close will be used for HTTP/1.0. + */ + private void configure() throws IOException { + long length = support.getContentLength(); + boolean empty = support.isEmpty(); + boolean tunnel = support.isTunnel(); + + if(tunnel) { + support.setConnectionUpgrade(); + } else if(empty) { + support.setContentLength(0); + } else if(length >= 0) { + support.setContentLength(length); + } else { + support.setChunkedEncoded(); + } + encoder = factory.getInstance(); + } + + /** + * This method is used to set the required HTTP headers on the + * response. This will check the existing HTTP headers, and if + * there is insufficient data chunked encoding will be used for + * HTTP/1.1 and connection close will be used for HTTP/1.0. + * + * @param count this is the number of bytes to be transferred + */ + private void configure(long count) throws IOException { + long length = support.getContentLength(); + + if(support.isHead()) { + if(count > 0) { + configure(count, count); + } else { + configure(count, length); + } + } else { + configure(count, count); + } + } + + /** + * This method is used to set the required HTTP headers on the + * response. This will check the existing HTTP headers, and if + * there is insufficient data chunked encoding will be used for + * HTTP/1.1 and connection close will be used for HTTP/1.0. + * + * @param count this is the number of bytes to be transferred + * @param length this is the actual length value to be used + */ + private void configure(long count, long length) throws IOException { + boolean empty = support.isEmpty(); + boolean tunnel = support.isTunnel(); + + if(tunnel) { + support.setConnectionUpgrade(); + } else if(empty) { + support.setContentLength(0); + } else if(length >= 0) { + support.setContentLength(length); + } else { + support.setChunkedEncoded(); + } + encoder = factory.getInstance(); + } + + /** + * This is used to clear any previous encoding that has been set + * in the event that content length may be used instead. This is + * used so that an override can be made to the transfer encoding + * such that content length can be used instead. + */ + private void clear() throws IOException { + support.setIdentityEncoded(); + + } + + /** + * This is used to compose the HTTP header and send it over the + * transport to the client. Once done the response is committed + * and no more headers can be set, also the semantics of the + * response have been committed and the encoder is created. + */ + private void commit() throws IOException { + try { + response.commit(); + } catch(Exception cause) { + throw new ResponseException("Unable to commit", cause); + } + } + +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseEntity.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseEntity.java new file mode 100644 index 0000000..ae7aed9 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseEntity.java @@ -0,0 +1,437 @@ +/* + * ResponseEntity.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static org.simpleframework.http.Protocol.CONTENT_LENGTH; +import static org.simpleframework.http.Protocol.CONTENT_TYPE; +import static org.simpleframework.http.core.ContainerEvent.WRITE_HEADER; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.channels.WritableByteChannel; +import java.util.Map; + +import org.simpleframework.http.ContentType; +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.http.message.Entity; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteWriter; +import org.simpleframework.transport.trace.Trace; + +/** + * This is used to represent the HTTP response. This provides methods + * that can be used to set various characteristics of the response. + * The <code>OutputStream</code> of the <code>Response</code> can be + * retrieved from this interface as can the I.P address of the client + * that will be receiving the <code>Response</code>. The attributes + * of the connection can be retrieved also. This provides a set of + * methods that can be used to set the attributes of the stream so + * the <code>Response</code> can be transported properly. The headers + * can be set and will be sent once a commit is made, or when there + * is content sent over the output stream. + * <p> + * This should never allow the message body be sent if it should not + * be sent with the headers as of RFC 2616 rules for the presence of + * a message body. A message body must not be included with a HEAD + * request or with a 304 or a 204 response. A proper implementation + * of this will prevent a message body being sent if the response + * is to a HEAD request of if there is a 304 or 204 response code. + * <p> + * It is important to note that the <code>Response</code> controls + * the processing of the HTTP pipeline. The next HTTP request is + * not processed until the response has committed. The response is + * committed once the <code>commit</code> method is invoked if there + * is NO content body. Committing with a content body is done only if + * correct content is given. The <code>OutputStream</code> acts as + * a client and commits the response once the specified content has + * been written to the issued <code>OutputStream</code>. + * + * @author Niall Gallagher + */ +class ResponseEntity extends ResponseMessage implements Response { + + /** + * This is the observer that is used to monitor the response. + */ + private BodyObserver observer; + + /** + * This is used to buffer the bytes that are sent to the client. + */ + private ResponseBuffer buffer; + + /** + * This is the conversation used to determine connection type. + */ + private Conversation support; + + /** + * This is the underlying channel for the connected pipeline. + */ + private Channel channel; + + /** + * This is the sender object used to deliver to response data. + */ + private ByteWriter sender; + + /** + * This is used to trace events that occur with the response + */ + private Trace trace; + + /** + * Constructor for the <code>ResponseEntity</code> object. This is + * used to create a response instance using the provided request, + * entity, and monitor object. To ensure that the response is + * compatible with client the <code>Request</code> is used. Also + * to ensure the next request can be processed the provided monitor + * is used to signal response events to the server kernel. + * + * @param observer this is the observer used to signal events + * @param request this is the request that was sent by the client + * @param entity this is the entity that contains the channel + */ + public ResponseEntity(BodyObserver observer, Request request, Entity entity) { + this.support = new Conversation(request, this); + this.buffer = new ResponseBuffer(observer, this, support, entity); + this.channel = entity.getChannel(); + this.sender = channel.getWriter(); + this.trace = channel.getTrace(); + this.observer = observer; + } + + /** + * This represents the time at which the response has fully written. + * Because the response is delivered asynchronously to the client + * this response time does not represent the time to last byte. + * It simply represents the time at which the response has been + * fully generated and written to the output buffer or queue. This + * returns zero if the response has not finished. + * + * @return this is the time taken to complete the response + */ + public long getResponseTime() { + return observer.getTime(); + } + + /** + * This is used as a shortcut for acquiring attributes for the + * response. This avoids acquiring the <code>Attributes</code> + * in order to retrieve the attribute directly from that object. + * The attributes contain data specific to the response. + * + * @param name this is the name of the attribute to acquire + * + * @return this returns the attribute for the specified name + */ + public Object getAttribute(Object name) { + return getAttributes().get(name); + } + + /** + * This can be used to retrieve certain attributes about + * this <code>Response</code>. The attributes contains certain + * properties about the <code>Response</code>. For example if + * this Response goes over a secure line then there may be any + * arbitrary attributes. + * + * @return the response attributes of that have been set + */ + public Map getAttributes() { + return channel.getAttributes(); + } + + /** + * This should be used when the size of the message body is known. For + * performance reasons this should be used so the length of the output + * is known. This ensures that Persistent HTTP (PHTTP) connections + * can be maintained for both HTTP/1.0 and HTTP/1.1 clients. If the + * length of the output is not known HTTP/1.0 clients will require a + * connection close, which reduces performance (see RFC 2616). + * <p> + * This removes any previous Content-Length headers from the message + * header. This will then set the appropriate Content-Length header with + * the correct length. If a the Connection header is set with the close + * token then the semantics of the connection are such that the server + * will close it once the <code>OutputStream.close</code> is used. + * + * @param length this is the length of the HTTP message body + */ + public void setContentLength(long length) { + setLong(CONTENT_LENGTH, length); + } + + /** + * This is used to set the content type for the response. Typically + * a response will contain a message body of some sort. This is used + * to conveniently set the type for that response. Setting the + * content type can also be done explicitly if desired. + * + * @param type this is the type that is to be set in the response + */ + public void setContentType(String type) { + setValue(CONTENT_TYPE, type); + } + + /** + * This determines the charset for <code>PrintStream</code> objects + * returned from the <code>getPrintStream</code> method. This will + * return a valid charset regardless of whether the Content-Type + * header has been set, set without a charset, or not set at all. + * If unspecified, the charset returned is <code>ISO-8859-1</code>, + * as suggested by RFC 2616, section 3.7.1. + * + * @return returns the charset used by this response object + */ + private String getCharset() { + ContentType type = getContentType(); + + if(type == null) { + return "ISO-8859-1"; + } + if(type.getCharset()==null){ + return "ISO-8859-1"; + } + return type.getCharset(); + } + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>OutputStream</code> will be determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * + * @return an output stream object used to write the message body + */ + public OutputStream getOutputStream() throws IOException { + return buffer; + } + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>OutputStream</code> will be determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * <p> + * This will ensure that there is buffering done so that the output + * can be reset using the <code>reset</code> method. This will + * enable the specified number of bytes to be written without + * committing the response. This specified size is the minimum size + * that the response buffer must be. + * + * @param size the minimum size that the response buffer must be + * + * @return an output stream object used to write the message body + */ + public OutputStream getOutputStream(int size) throws IOException { + if(size > 0) { + buffer.expand(size); + } + return buffer; + } + + /** + * This method is provided for convenience so that the HTTP content + * can be written using the <code>print</code> methods provided by + * the <code>PrintStream</code>. This will basically wrap the + * <code>getOutputStream</code> with a buffer size of zero. + * <p> + * The retrieved <code>PrintStream</code> uses the charset used to + * describe the content, with the Content-Type header. This will + * check the charset parameter of the contents MIME type. So if + * the Content-Type was <code>text/plain; charset=UTF-8</code> the + * resulting <code>PrintStream</code> would encode the written data + * using the UTF-8 encoding scheme. Care must be taken to ensure + * that bytes written to the stream are correctly encoded. + * + * @return a print stream object used to write the message body + */ + public PrintStream getPrintStream() throws IOException { + return getPrintStream(0, getCharset()); + } + + /** + * This method is provided for convenience so that the HTTP content + * can be written using the <code>print</code> methods provided by + * the <code>PrintStream</code>. This will basically wrap the + * <code>getOutputStream</code> with a specified buffer size. + * <p> + * The retrieved <code>PrintStream</code> uses the charset used to + * describe the content, with the Content-Type header. This will + * check the charset parameter of the contents MIME type. So if + * the Content-Type was <code>text/plain; charset=UTF-8</code> the + * resulting <code>PrintStream</code> would encode the written data + * using the UTF-8 encoding scheme. Care must be taken to ensure + * that bytes written to the stream are correctly encoded. + * + * @param size the minimum size that the response buffer must be + * + * @return a print stream object used to write the message body + */ + public PrintStream getPrintStream(int size) throws IOException { + return getPrintStream(size, getCharset()); + } + + /** + * This is used to wrap the <code>getOutputStream</code> object in + * a <code>PrintStream</code>, which will write content using a + * specified charset. The <code>PrintStream</code> created will not + * buffer the content, it will write directly to the underlying + * <code>OutputStream</code> where it is buffered (if there is a + * buffer size greater than zero specified). In future the buffer + * of the <code>PrintStream</code> may be usable. + * + * @param size the minimum size that the response buffer must be + * @param charset this is the charset used by the resulting stream + * + * @return a print stream that encodes in the given charset + */ + private PrintStream getPrintStream(int size, String charset) throws IOException { + if(size > 0) { + buffer.expand(size); + } + return new PrintStream(buffer, false, charset); + } + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>WritableByteChannel</code> are determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * + * @return a writable byte channel used to write the message body + */ + public WritableByteChannel getByteChannel() throws IOException { + return buffer; + } + + /** + * Used to write a message body with the <code>Response</code>. The + * semantics of this <code>WritableByteChannel</code> are determined + * by the HTTP version of the client, and whether or not the content + * length has been set, through the <code>setContentLength</code> + * method. If the length of the output is not known then the output + * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients. + * <p> + * This will ensure that there is buffering done so that the output + * can be reset using the <code>reset</code> method. This will + * enable the specified number of bytes to be written without + * committing the response. This specified size is the minimum size + * that the response buffer must be. + * + * @param size the minimum size that the response buffer must be + * + * @return a writable byte channel used to write the message body + */ + public WritableByteChannel getByteChannel(int size) throws IOException { + if(size > 0) { + buffer.expand(size); + } + return buffer; + } + + /** + * This is used to determine if the HTTP response message is a + * keep alive message or if the underlying socket was closed. Even + * if the client requests a connection keep alive and supports + * persistent connections, the response can still be closed by + * the server. This can be explicitly indicated by the presence + * of the <code>Connection</code> HTTP header, it can also be + * implicitly indicated by using version HTTP/1.0. + * + * @return this returns true if the connection was closed + */ + public boolean isKeepAlive() { + return support.isKeepAlive(); + } + + /** + * This can be used to determine whether the <code>Response</code> + * has been committed. This is true if the <code>Response</code> + * was committed, either due to an explicit invocation of the + * <code>commit</code> method or due to the writing of content. If + * the <code>Response</code> has committed the <code>reset</code> + * method will not work in resetting content already written. + * + * @return true if the response has been fully committed + */ + public boolean isCommitted() { + return observer.isCommitted(); + } + + /** + * This is used to write the headers that where given to the + * <code>Response</code>. Any further attempts to give headers + * to the <code>Response</code> will be futile as only the headers + * that were given at the time of the first commit will be used + * in the message header. + * <p> + * This also performs some final checks on the headers submitted. + * This is done to determine the optimal performance of the + * output. If no specific Connection header has been specified + * this will set the connection so that HTTP/1.0 closes by default. + * + * @exception IOException thrown if there was a problem writing + */ + public void commit() throws IOException { + if(!observer.isCommitted()) { + String header = toString(); + byte[] message = header.getBytes("UTF-8"); + + trace.trace(WRITE_HEADER, header); + sender.write(message); + observer.commit(sender); + } + } + + /** + * This can be used to determine whether the <code>Response</code> + * has been committed. This is true if the <code>Response</code> + * was committed, either due to an explicit invocation of the + * <code>commit</code> method or due to the writing of content. If + * the <code>Response</code> has committed the <code>reset</code> + * method will not work in resetting content already written. + * + * @throws IOException thrown if there is a problem resetting + */ + public void reset() throws IOException { + buffer.reset(); + } + + /** + * This is used to close the connection and commit the request. + * This provides the same semantics as closing the output stream + * and ensures that the HTTP response is committed. This will + * throw an exception if the response can not be committed. + * + * @throws IOException thrown if there is a problem writing + */ + public void close() throws IOException { + buffer.close(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseException.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseException.java new file mode 100644 index 0000000..a02fe78 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseException.java @@ -0,0 +1,58 @@ +/* + * ResponseException.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import java.io.IOException; + +/** + * The <code>ResponseException</code> object is used to represent an + * exception that is thrown when there is a problem producing the + * response body. This can be used to wrap <code>IOException</code> + * objects that are thrown from the underlying transport. + * + * @author Niall Gallagher + */ +class ResponseException extends IOException { + + /** + * Constructor for the <code>ResponseException</code> object. This + * is used to represent an exception that is thrown when producing + * the response body. The producer exception is an I/O exception + * and thus exceptions can propagate out of stream methods. + * + * @param message this is the message describing the exception + */ + public ResponseException(String message) { + super(message); + } + + /** + * Constructor for the <code>ResponseException</code> object. This + * is used to represent an exception that is thrown when producing + * the response body. The producer exception is an I/O exception + * and thus exceptions can propagate out of stream methods. + * + * @param message this is the message describing the exception + * @param cause this is the cause of the producer exception + */ + public ResponseException(String message, Throwable cause) { + super(message); + initCause(cause); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseMessage.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseMessage.java new file mode 100644 index 0000000..4dcfb08 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseMessage.java @@ -0,0 +1,283 @@ +/* + * ResponseMessage.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static org.simpleframework.http.Protocol.CONTENT_LENGTH; +import static org.simpleframework.http.Protocol.CONTENT_TYPE; +import static org.simpleframework.http.Protocol.SET_COOKIE; +import static org.simpleframework.http.Protocol.TRANSFER_ENCODING; + +import org.simpleframework.http.ContentType; +import org.simpleframework.http.Cookie; +import org.simpleframework.http.ResponseHeader; +import org.simpleframework.http.Status; +import org.simpleframework.http.message.MessageHeader; +import org.simpleframework.http.parse.ContentTypeParser; + +/** + * The <code>ResponseMessage</code> object represents the header used + * for a response. This is used to get and set the headers in a case + * insensitive manner. It is also used to manage the cookies that are + * send and received. Also, the status code and description can also + * be set through this object as well as the protocol version. + * + * @author Niall Gallagher + */ +class ResponseMessage extends MessageHeader implements ResponseHeader { + + /** + * This is the text description used for the response status. + */ + private String text; + + /** + * This is the major protocol version used for the response. + */ + private int major; + + /** + * This is the minor protocol version used for the response. + */ + private int minor; + + /** + * This is the status code used to identify the response type. + */ + private int code; + + /** + * Constructor for the <code>ResponseMessage</code> object. This + * is used to create a response message with a default status code + * of 200 and a a protocol version of HTTP/1.1. If the response is + * a different status code or version these can be modified. + */ + public ResponseMessage() { + this.text = "OK"; + this.code = 200; + this.major = 1; + this.minor = 1; + } + + /** + * This represents the status code of the HTTP response. + * The response code represents the type of message that is + * being sent to the client. For a description of the codes + * see RFC 2616 section 10, Status Code Definitions. + * + * @return the status code that this HTTP response has + */ + public int getCode() { + return code; + } + + /** + * This method allows the status for the response to be + * changed. This MUST be reflected the the response content + * given to the client. For a description of the codes see + * RFC 2616 section 10, Status Code Definitions. + * + * @param code the new status code for the HTTP response + */ + public void setCode(int code) { + this.code = code; + } + + /** + * This can be used to retrieve the text of a HTTP status + * line. This is the text description for the status code. + * This should match the status code specified by the RFC. + * + * @return the message description of the response + */ + public String getDescription() { + return text; + } + + /** + * This is used to set the text of the HTTP status line. + * This should match the status code specified by the RFC. + * + * @param text the descriptive text message of the status + */ + public void setDescription(String text) { + this.text = text; + } + + /** + * This is used to acquire the status from the response. + * The <code>Status</code> object returns represents the + * code that has been set on the response, it does not + * necessarily represent the description in the response. + * + * @return this is the response for this status line + */ + public Status getStatus() { + return Status.getStatus(code); + } + + /** + * This is used to set the status code and description + * for this response. Setting the code and description in + * this manner provides a much more convenient way to set + * the response status line details. + * + * @param status this is the status to set on the response + */ + public void setStatus(Status status) { + setCode(status.code); + setDescription(status.description); + } + + /** + * This can be used to get the major number from a HTTP version. + * The major version corresponds to the major type that is the 1 + * of a HTTP/1.0 version string. + * + * @return the major version number for the request message + */ + public int getMajor() { + return major; + } + + /** + * This can be used to set the major number from a HTTP version. + * The major version corresponds to the major type that is the 1 + * of a HTTP/1.0 version string. + * + * @param major the major version number for the request message + */ + public void setMajor(int major) { + this.major = major; + } + + /** + * This can be used to get the minor number from a HTTP version. + * The minor version corresponds to the major type that is the 0 + * of a HTTP/1.0 version string. This is used to determine if + * the request message has keep alive semantics. + * + * @return the minor version number for the request message + */ + public int getMinor() { + return minor; + } + + /** + * This can be used to get the minor number from a HTTP version. + * The minor version corresponds to the major type that is the 0 + * of a HTTP/1.0 version string. This is used to determine if + * the request message has keep alive semantics. + * + * @param minor the minor version number for the request message + */ + public void setMinor(int minor) { + this.minor = minor; + } + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Type</code> header, if there is then + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content type value if it exists + */ + public ContentType getContentType() { + String value = getValue(CONTENT_TYPE); + + if(value == null) { + return null; + } + return new ContentTypeParser(value); + } + + /** + * This is a convenience method that can be used to determine + * the length of the message body. This will determine if there + * is a <code>Content-Length</code> header, if it does then the + * length can be determined, if not then this returns -1. + * + * @return content length, or -1 if it cannot be determined + */ + public long getContentLength() { + return getLong(CONTENT_LENGTH); + } + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Transfer-Encoding</code> header, if there is + * then this will parse that header and return the first token in + * the comma separated list of values, which is the primary value. + * + * @return this returns the transfer encoding value if it exists + */ + public String getTransferEncoding() { + return getValue(TRANSFER_ENCODING); + } + + /** + * This is used to compose the HTTP response header. All of the + * headers added to the response are added, as well as the cookies + * to form the response message header. To ensure that the text + * produces is as required the header names are in the same case + * as they were added to the response message. + * + * @return a string representation of the response message + */ + public CharSequence getHeader() { + return toString(); + } + + /** + * This is used to compose the HTTP response header. All of the + * headers added to the response are added, as well as the cookies + * to form the response message header. To ensure that the text + * produces is as required the header names are in the same case + * as they were added to the response message. + * + * @return a string representation of the response message + */ + public String toString() { + StringBuilder head = new StringBuilder(256); + + head.append("HTTP/").append(major); + head.append('.').append(minor); + head.append(' ').append(code); + head.append(' ').append(text); + head.append("\r\n"); + + for(String name : getNames()) { + for(String value : getAll(name)) { + head.append(name); + head.append(": "); + head.append(value); + head.append("\r\n"); + } + } + for(Cookie cookie : getCookies()) { + head.append(SET_COOKIE); + head.append(": "); + head.append(cookie); + head.append("\r\n"); + } + return head.append("\r\n").toString(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseObserver.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseObserver.java new file mode 100644 index 0000000..4a4b2c7 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/ResponseObserver.java @@ -0,0 +1,238 @@ +/* + * ResponseObserver.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.simpleframework.http.core.ContainerEvent.ERROR; +import static org.simpleframework.http.core.ContainerEvent.RESPONSE_FINISHED; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.simpleframework.http.message.Entity; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteWriter; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>ResponseObserver</code> is used to observe the response + * streams. If there is an error or a close requested this will + * close the underlying transport. If however there is a successful + * response then this will flush the transport and hand the channel + * for the pipeline back to the server kernel. This ensures that + * the next HTTP request can be consumed from the transport. + * + * @author Niall Gallagher + */ +class ResponseObserver implements BodyObserver { + + /** + * This is used to determine if the response has committed. + */ + private AtomicBoolean committed; + + /** + * This flag determines whether the connection was closed. + */ + private AtomicBoolean closed; + + /** + * This flag determines whether the was a response error. + */ + private AtomicBoolean error; + + /** + * This is the controller used to initiate a new request. + */ + private Controller controller; + + /** + * This is the channel associated with the client connection. + */ + private Channel channel; + + /** + * This is the trace used to observe the state of the stream. + */ + private Trace trace; + + /** + * This represents a time stamp that records the finish time. + */ + private Timer timer; + + /** + * Constructor for the <code>ResponseObserver</code> object. This + * is used to create an observer using a HTTP request entity and an + * initiator which is used to reprocess a channel if there was a + * successful deliver of a response. + * + * @param controller the controller used to process channels + * @param entity this is the entity associated with the channel + */ + public ResponseObserver(Controller controller, Entity entity) { + this.timer = new Timer(MILLISECONDS); + this.committed = new AtomicBoolean(); + this.closed = new AtomicBoolean(); + this.error = new AtomicBoolean(); + this.channel = entity.getChannel(); + this.trace = channel.getTrace(); + this.controller = controller; + } + + /** + * This is used to close the underlying transport. A closure is + * typically done when the response is to a HTTP/1.0 client + * that does not require a keep alive connection. Also, if the + * container requests an explicit closure this is used when all + * of the content for the response has been sent. + * + * @param writer this is the writer used to send the response + */ + public void close(ByteWriter writer) { + try { + if(!isClosed()) { + closed.set(true); + timer.set(); + trace.trace(RESPONSE_FINISHED); + writer.close(); + } + } catch(Exception cause) { + trace.trace(ERROR, cause); + fail(writer); + } + } + + /** + * This is used when there is an error sending the response. On + * error RFC 2616 suggests a connection closure is the best + * means to handle the condition, and the one clients should be + * expecting and support. All errors result in closure of the + * underlying transport and no more requests are processed. + * + * @param writer this is the writer used to send the response + */ + public void error(ByteWriter writer) { + try { + if(!isClosed()) { + error.set(true); + timer.set(); + trace.trace(RESPONSE_FINISHED); + writer.close(); + } + } catch(Exception cause) { + trace.trace(ERROR, cause); + fail(writer); + } + } + + /** + * This is used when the response has been sent correctly and + * the connection supports persisted HTTP. When ready the channel + * is handed back in to the server kernel where the next request + * on the pipeline is read and used to compose the next entity. + * + * @param writer this is the writer used to send the response + */ + public void ready(ByteWriter writer) { + try { + if(!isClosed()) { + closed.set(true); + writer.flush(); + timer.set(); + trace.trace(RESPONSE_FINISHED); + controller.start(channel); + } + } catch(Exception cause) { + trace.trace(ERROR, cause); + fail(writer); + } + } + + /** + * This is used to purge the writer so that it closes the socket + * ensuring there is no connection leak on shutdown. This is used + * when there is an exception signalling the state of the writer. + * + * @param writer this is the writer that is to be purged + */ + private void fail(ByteWriter writer) { + try { + writer.close(); + } catch(Exception cause) { + trace.trace(ERROR, cause); + } + } + + /** + * This is used to notify the observer that the HTTP response is + * committed and that the header can no longer be changed. It + * is also used to indicate whether the response can be reset. + * + * @param writer this is the writer used to send the response + */ + public void commit(ByteWriter writer) { + committed.set(true); + } + + /** + * This can be used to determine whether the response has been + * committed. If the response is committed then the header can + * no longer be manipulated and the response has been partially + * send to the client. + * + * @return true if the response headers have been committed + */ + public boolean isCommitted() { + return committed.get(); + } + + /** + * This is used to determine if the response has completed or + * if there has been an error. This basically allows the writer + * of the response to take action on certain I/O events. + * + * @return this returns true if there was an error or close + */ + public boolean isClosed() { + return closed.get() || error.get(); + } + + /** + * This is used to determine if the response was in error. If + * the response was in error this allows the writer to throw an + * exception indicating that there was a problem responding. + * + * @return this returns true if there was a response error + */ + public boolean isError(){ + return error.get(); + } + + /** + * This represents the time at which the response was either + * ready, closed or in error. Providing a time here is useful + * as it allows the time taken to generate a response to be + * determined even if the response is written asynchronously. + * + * @return the time when the response completed or failed + */ + public long getTime() { + return timer.get(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/core/Timer.java b/simple/simple-http/src/main/java/org/simpleframework/http/core/Timer.java new file mode 100644 index 0000000..c07d49b --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/core/Timer.java @@ -0,0 +1,94 @@ +/* + * Timer.java November 2012 + * + * Copyright (C) 2012, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.core; + +import static java.lang.System.currentTimeMillis; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.TimeUnit; + +/** + * The <code>Timer<code> object is used to set the time a specific + * event occurred at. The time can be set only once from that point + * on all attempts to set the time are ignored. This makes this + * timer useful when there is a desire to record when a certain + * scenario was first encountered, for example when a request is + * first read from the underlying transport. + * + * @author Niall Gallagher + */ +class Timer { + + /** + * This is the time unit that this timer provides the time in. + */ + private TimeUnit unit; + + /** + * This is the time in milliseconds used to record the event. + */ + private volatile long time; + + /** + * Constructor for the <code>Timer</code> object. This is used + * to record when a specific event occurs. The provided time + * unit is used to determine how the time is retrieved. + * + * @param unit this time unit this timer will be using + */ + public Timer(TimeUnit unit) { + this.unit = unit; + this.time = -1L; + } + + /** + * This is used to determine if the timer has been set. If + * the <code>set</code> method has been called on this instance + * before then this will return true, otherwise false. + * + * @return this returns true if the timer has been set + */ + public boolean isSet() { + return time > 0; + } + + /** + * This is used to set the time for a specific event. Invoking + * this method multiple times will have no effect as the time + * is set for the first invocation only. Setting the time in + * this manner enables start times to be recorded effectively. + */ + public void set() { + if(time < 0) { + time = currentTimeMillis(); + } + } + + /** + * This is used to get the time for a specific event. The time + * returned by this method is given in the time unit specified + * on construction of the instance. + * + * @return this returns the time recorded by the timer + */ + public long get() { + return unit.convert(time, MILLISECONDS); + } +} +
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/ArrayConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/ArrayConsumer.java new file mode 100644 index 0000000..ef5db66 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/ArrayConsumer.java @@ -0,0 +1,184 @@ +/* + * ArrayConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>ArrayConsumer</code> object is a consumer that consumes + * bytes in to an internal array before processing. This consumes + * all bytes read in to an internal array. Each read is met with an + * invocation of the <code>scan</code> method, which searches for + * the terminal token within the read chunk. Once the terminal token + * has been read the excess bytes are reset and the data can be + * processed by the subclass implementation. The internal array is + * expanded if the number of consumed bytes exceeds its capacity. + * + * @author Niall Gallagher + */ +public abstract class ArrayConsumer implements ByteConsumer { + + /** + * This is the array that is used to contain the read bytes. + */ + protected byte[] array; + + /** + * This is the number of bytes that have been consumed so far. + */ + protected int count; + + /** + * This is the size of the chunk of bytes to read each time. + */ + protected int chunk; + + /** + * This determines whether the terminal token has been read. + */ + protected boolean done; + + /** + * Constructor for the <code>ArrayConsumer</code> object. This is + * used to create a consumer that will consume all bytes in to an + * internal array until a terminal token has been read. If excess + * bytes are read by this consumer they are reset in the cursor. + */ + public ArrayConsumer() { + this(1024); + } + + /** + * Constructor for the <code>ArrayConsumer</code> object. This is + * used to create a consumer that will consume all bytes in to an + * internal array until a terminal token has been read. If excess + * bytes are read by this consumer they are reset in the cursor. + * + * @param size this is the initial array and chunk size to use + */ + public ArrayConsumer(int size) { + this(size, 512); + } + + /** + * Constructor for the <code>ArrayConsumer</code> object. This is + * used to create a consumer that will consume all bytes in to an + * internal array until a terminal token has been read. If excess + * bytes are read by this consumer they are reset in the cursor. + * + * @param size this is the initial array size that is to be used + * @param chunk this is the chunk size to read bytes as + */ + public ArrayConsumer(int size, int chunk) { + this.array = new byte[size]; + this.chunk = chunk; + } + + /** + * This method is used to consume bytes from the provided cursor. + * Each read performed is done in a specific chunk size to ensure + * that a sufficiently large or small amount of data is read from + * the <code>ByteCursor</code> object. After each read the byte + * array is scanned for the terminal token. When the terminal + * token is found the bytes are processed by the implementation. + * + * @param cursor this is the cursor to consume the bytes from + */ + public void consume(ByteCursor cursor) throws IOException { + if(!done) { + int ready = cursor.ready(); + + while(ready > 0) { + int size = Math.min(ready, chunk); + + if(count + size > array.length) { + resize(count + size); + } + size = cursor.read(array, count, size); + count += size; + + if(size > 0) { + int reset = scan(); + + if(reset > 0) { + cursor.reset(reset); + } + if(done) { + process(); + break; + } + } + ready = cursor.ready(); + } + } + } + + /** + * This method is used to add an additional chunk size to the + * internal array. Resizing of the internal array is required as + * the consumed bytes may exceed the initial size of the array. + * In such a scenario the array is expanded the chunk size. + * + * @param size this is the minimum size to expand the array to + */ + protected void resize(int size) throws IOException { + if(array.length < size) { + int expand = array.length + chunk; + int max = Math.max(expand, size); + byte[] temp = new byte[max]; + + System.arraycopy(array, 0, temp, 0, count); + array = temp; + } + } + + /** + * When the terminal token is read from the cursor this will be + * true. The <code>scan</code> method is used to determine the + * terminal token. It is invoked after each read, when the scan + * method returns a non-zero value then excess bytes are reset + * and the consumer has finished. + * + * @return this returns true when the terminal token is read + */ + public boolean isFinished() { + return done; + } + + /** + * This method is invoked after the terminal token has been read. + * It is used to process the consumed data and is typically used to + * parse the input such that it can be used by the subclass for + * some useful purpose. This is called only once by the consumer. + */ + protected abstract void process() throws IOException; + + /** + * This method is used to scan for the terminal token. It searches + * for the token and returns the number of bytes in the buffer + * after the terminal token. Returning the excess bytes allows the + * consumer to reset the bytes within the consumer object. + * + * @return this returns the number of excess bytes consumed + */ + protected abstract int scan() throws IOException; + +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/Body.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/Body.java new file mode 100644 index 0000000..6b3599a --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/Body.java @@ -0,0 +1,95 @@ +/* + * Body.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import org.simpleframework.http.Part; + +/** + * The <code>Body</code> interface is used to represent the body of + * a HTTP entity. It contains the information that is delivered with + * the request. The body is represented by a stream of bytes. In + * order to access the entity body this interface provides a stream + * which can be used to read it. Also, should the message be encoded + * as a multipart message the individual parts can be read using the + * <code>Attachment</code> instance for it. + * + * @author Niall Gallagher + */ +public interface Body { + + /** + * This will acquire the contents of the body in UTF-8. If there + * is no content encoding and the user of the request wants to + * deal with the body as a string then this method can be used. + * It will simply create a UTF-8 string using the body bytes. + * + * @return returns a UTF-8 string representation of the body + */ + String getContent() throws IOException; + + /** + * This will acquire the contents of the body in the specified + * charset. Typically this will be given the charset as taken + * from the HTTP Content-Type header. Although any encoding can + * be specified to convert the body to a string representation. + * + * @return returns an encoded string representation of the body + */ + String getContent(String charset) throws IOException; + + /** + * This is used to acquire the contents of the body as a stream. + * Each time this method is invoked a new stream is created that + * will read the contents of the body from the first byte. This + * ensures that the stream can be acquired several times without + * any issues arising from previous reads. + * + * @return this returns a new string used to read the body + */ + InputStream getInputStream() throws IOException; + + /** + * This method is used to acquire a <code>Part</code> from the + * HTTP request using a known name for the part. This is typically + * used when there is a file upload with a multipart POST request. + * All parts that are not files can be acquired as string values + * from the attachment object. + * + * @param name this is the name of the part object to acquire + * + * @return the named part or null if the part does not exist + */ + Part getPart(String name); + + /** + * This method is used to get all <code>Part</code> objects that + * are associated with the request. Each attachment contains the + * body and headers associated with it. If the request is not a + * multipart POST request then this will return an empty list. + * + * @return the list of parts associated with this request + */ + List<Part> getParts(); +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/BodyConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/BodyConsumer.java new file mode 100644 index 0000000..d84d6ed --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/BodyConsumer.java @@ -0,0 +1,43 @@ +/* + * BodyConsumer.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +/** + * The <code>BodyConsumer</code> is used to consume the body of an + * HTTP message. Implementations of this consumer must provide the + * <code>Body</code> that has been consumed. If there is no body + * associated with the consumer then an empty body is returned. + * + * @author Niall Gallagher + */ +public interface BodyConsumer extends ByteConsumer { + + /** + * This is used to acquire the body that has been consumed. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Part</code> objects. + * Each part can then be read as an individual message. + * + * @return the body that has been consumed by this instance + */ + Body getBody(); +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/BoundaryConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/BoundaryConsumer.java new file mode 100644 index 0000000..f519ce2 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/BoundaryConsumer.java @@ -0,0 +1,206 @@ +/* + * BoundaryConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.Buffer; + +/** + * The <code>BoundaryConsumer</code> is used to consume a boundary + * for a multipart message. This ensures that the boundary complies + * with the multipart specification in that it ends with a carriage + * return and line feed. This consumer implementation can be used + * multiple times as its internal buffer can be cleared and reset. + * + * @author Niall Gallagher + */ +class BoundaryConsumer extends ArrayConsumer { + + /** + * This is the terminal token for a multipart boundary entity. + */ + private static final byte[] LAST = { '-', '-', '\r', '\n', }; + + /** + * This is the terminal token for a multipart boundary line. + */ + private static final byte[] LINE = { '\r', '\n' }; + + /** + * This represents the start of the boundary line for the part. + */ + private static final byte[] TOKEN = { '-', '-' }; + + /** + * This is used to allocate a buffer for for the boundary. + */ + private Allocator allocator; + + /** + * This is used to consume the contents of the consumed buffer. + */ + private Buffer buffer; + + /** + * This is the actual boundary value that is to be consumed. + */ + private byte[] boundary; + + /** + * This counts the number of characters read from the start. + */ + private int seek; + + /** + * Constructor for the <code>BoundaryConsumer</code> object. This + * is used to create a boundary consumer for validating boundaries + * and consuming them from a provided source. This is used to help + * in reading multipart messages by removing boundaries from the + * stream. + * + * @param boundary this is the boundary value to be consumed + */ + public BoundaryConsumer(Allocator allocator, byte[] boundary) { + this.chunk = boundary.length + LAST.length + TOKEN.length; + this.allocator = allocator; + this.boundary = boundary; + } + + /** + * This does not perform any processing after the boundary has + * been consumed. Because the boundary consumer is used only as a + * means to remove the boundary from the underlying stream there + * is no need to perform any processing of the value consumed. + */ + @Override + protected void process() throws IOException { + if(count < boundary.length + 4) { + throw new IOException("Invalid boundary processed"); + } + } + + /** + * This method is used to scan for the terminal token. It searches + * for the token and returns the number of bytes in the buffer + * after the terminal token. Returning the excess bytes allows the + * consumer to reset the bytes within the consumer object. + * + * @return this returns the number of excess bytes consumed + */ + @Override + protected int scan() throws IOException { + int size = boundary.length; + + if(count >= 2 && seek < 2) { + if(scan(TOKEN)) { + append(TOKEN); + } + } + if(count >= 2 + size && seek < 2 + size) { + if(scan(boundary)) { + append(boundary); + } + } + if(count >= 4 + size && seek < 4 + size) { + if(array[size + 2] == TOKEN[0]) { + if(scan(TOKEN)) { + append(TOKEN); + } + } else if(array[size + 2] == LINE[0]) { + if(scan(LINE)) { + append(LINE); + } + done = true; + return count - seek; + } + } + if(count >= 6 + size && seek < 6 + size) { + if(scan(LINE)) { + append(LINE); + } + done = true; + return count - seek; + } + return 0; + } + + /** + * This is used to append a token to the underlying buffer. Adding + * various tokens ensures that the whole message is reconstructed + * and can be forwarded to any connected service if used as a proxy. + * + * @param token this is the token that is to be appended + */ + private void append(byte[] token) throws IOException { + if(buffer == null) { + buffer = allocator.allocate(chunk); + } + buffer.append(token); + } + + /** + * This is used to scan the specified token from the consumed bytes. + * If the data scanned does not match the token provided then this + * will throw an exception to signify a bad boundary. This will + * return true only when the whole boundary has been consumed. + * + * @param data this is the token to scan from the consumed bytes + * + * @return this returns true of the token has been read + */ + private boolean scan(byte[] data) throws IOException { + int size = data.length; + int pos = 0; + + while(seek < count) { + if(array[seek++] != data[pos++]) { + throw new IOException("Invalid boundary"); + } + if(pos == data.length) { + return true; + } + } + return pos == size; + } + + /** + * This is used to determine whether the boundary has been read + * from the underlying stream. This is true only when the very + * last boundary has been read. This will be the boundary value + * that ends with the two <code>-</code> characters. + * + * @return this returns true with the terminal boundary is read + */ + public boolean isEnd() { + return seek == chunk; + } + + /** + * This is used to clear the state of the of boundary consumer + * such that it can be reused. This is required as the multipart + * body may contain many parts, all delimited with the same + * boundary. Clearing allows the next boundary to be consumed. + */ + public void clear() { + done = false; + count = seek = 0; + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/BufferBody.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/BufferBody.java new file mode 100644 index 0000000..e0e8a75 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/BufferBody.java @@ -0,0 +1,166 @@ +/* + * BufferBody.java February 2012 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; + +import org.simpleframework.common.buffer.Buffer; +import org.simpleframework.http.Part; + +/** + * The <code>Body</code> interface is used to represent the body of + * a HTTP entity. It contains the information that is delivered with + * the request. The body is represented by a stream of bytes. In + * order to access the entity body this interface provides a stream + * which can be used to read it. Also, should the message be encoded + * as a multipart message the individual parts can be read using the + * <code>Attachment</code> instance for it. + * + * @author Niall Gallagher + */ +class BufferBody implements Body { + + /** + * This is used to hold the attachments for the HTTP body. + */ + private final PartSeries series; + + /** + * This is usd to hold the bytes representing the HTTP body. + */ + private final Buffer buffer; + + /** + * Constructor for the <code>BufferBody</code> object. This is + * used to create a body that represents a HTTP payload. The + * body enables the payload to be either read in a stream or + * as an encoded string. Also the attachments are available. + */ + public BufferBody() { + this(null); + } + + /** + * Constructor for the <code>BufferBody</code> object. This is + * used to create a body that represents a HTTP payload. The + * body enables the payload to be either read in a stream or + * as an encoded string. Also the attachments are available. + * + * @param buffer this is the buffer representing the body + */ + public BufferBody(Buffer buffer) { + this(buffer, null); + } + + /** + * Constructor for the <code>BufferBody</code> object. This is + * used to create a body that represents a HTTP payload. The + * body enables the payload to be either read in a stream or + * as an encoded string. Also the attachments are available. + * + * @param buffer this is the buffer representing the body + * @param series this is the list of parts for this body + */ + public BufferBody(Buffer buffer, PartSeries series) { + this.buffer = buffer; + this.series = series; + } + + /** + * This method is used to acquire a <code>Part</code> from the + * HTTP request using a known name for the part. This is typically + * used when there is a file upload with a multipart POST request. + * All parts that are not files can be acquired as string values + * from the attachment object. + * + * @param name this is the name of the part object to acquire + * + * @return the named part or null if the part does not exist + */ + public Part getPart(String name) { + if(series != null) { + return series.getPart(name); + } + return null; + } + + /** + * This method is used to get all <code>Part</code> objects that + * are associated with the request. Each attachment contains the + * body and headers associated with it. If the request is not a + * multipart POST request then this will return an empty list. + * + * @return the list of parts associated with this request + */ + public List<Part> getParts() { + if(series != null) { + return series.getParts(); + } + return Collections.emptyList(); + } + + /** + * This will acquire the contents of the body in UTF-8. If there + * is no content encoding and the user of the request wants to + * deal with the body as a string then this method can be used. + * It will simply create a UTF-8 string using the body bytes. + * + * @return returns a UTF-8 string representation of the body + */ + public String getContent() throws IOException { + if(buffer == null) { + return new String(); + } + return buffer.encode(); + } + + /** + * This will acquire the contents of the body in the specified + * charset. Typically this will be given the charset as taken + * from the HTTP Content-Type header. Although any encoding can + * be specified to convert the body to a string representation. + * + * @return returns an encoded string representation of the body + */ + public String getContent(String charset) throws IOException { + if(buffer == null) { + return new String(); + } + return buffer.encode(charset); + } + + /** + * This is used to acquire the contents of the body as a stream. + * Each time this method is invoked a new stream is created that + * will read the contents of the body from the first byte. This + * ensures that the stream can be acquired several times without + * any issues arising from previous reads. + * + * @return this returns a new string used to read the body + */ + public InputStream getInputStream() throws IOException { + if(buffer == null) { + return new EmptyInputStream(); + } + return buffer.open(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/BufferPart.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/BufferPart.java new file mode 100644 index 0000000..73a5ea0 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/BufferPart.java @@ -0,0 +1,160 @@ +/* + * BufferPart.java February 2012 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; +import java.io.InputStream; + +import org.simpleframework.common.buffer.Buffer; +import org.simpleframework.http.ContentDisposition; +import org.simpleframework.http.ContentType; +import org.simpleframework.http.Part; + +/** + * The <code>BufferPart</code> is used to represent a part within + * a request message. Typically a part represents either a text + * parameter or a file, with associated headers. The contents of + * the part can be acquire as an <code>InputStream</code> or as a + * string encoded in the default HTTP encoding ISO-8859-1 or in + * the encoding specified with the Content-Type header. + * + * @author Niall Gallagher + */ +class BufferPart implements Part { + + /** + * This is the segment representing the headers for the part. + */ + private final Segment segment; + + /** + * This is the body that forms the payload for the part. + */ + private final Body body; + + /** + * Constructor for the <code>BufferPart</code> object. This is + * used to create a part from a multipart body. Each part will + * contain the headers associated with it as well as the body. + * + * @param segment this holds the headers for the part + * @param buffer this represents the body for the part + */ + public BufferPart(Segment segment, Buffer buffer) { + this.body = new BufferBody(buffer); + this.segment = segment; + } + + /** + * This method is used to determine the type of a part. Typically + * a part is either a text parameter or a file. If this is true + * then the content represented by the associated part is a file. + * + * @return this returns true if the associated part is a file + */ + public boolean isFile() { + return getDisposition().isFile(); + } + + /** + * This method is used to acquire the name of the part. Typically + * this is used when the part represents a text parameter rather + * than a file. However, this can also be used with a file part. + * + * @return this returns the name of the associated part + */ + public String getName() { + return getDisposition().getName(); + } + + /** + * This method is used to acquire the file name of the part. This + * is used when the part represents a text parameter rather than + * a file. However, this can also be used with a file part. + * + * @return this returns the file name of the associated part + */ + public String getFileName() { + return getDisposition().getFileName(); + } + + /** + * This is used to acquire the content of the part as a string. + * The encoding of the string is taken from the content type. + * If no content type is sent the content is decoded in the + * standard default of ISO-8859-1. + * + * @return this returns a string representing the content + * + * @throws IOException thrown if the content can not be created + */ + public String getContent() throws IOException { + return body.getContent(); + } + + /** + * This is used to acquire an <code>InputStream</code> for the + * part. Acquiring the stream allows the content of the part to + * be consumed by reading the stream. Each invocation of this + * method will produce a new stream starting from the first byte. + * + * @return this returns the stream for this part object + * + * @throws IOException thrown if the stream can not be created + */ + public InputStream getInputStream() throws IOException { + return body.getInputStream(); + } + + /** + * This is used to acquire the content type for this part. This + * is typically the type of content for a file part, as provided + * by a MIME type from the HTTP "Content-Type" header. + * + * @return this returns the content type for the part object + */ + public ContentType getContentType() { + return segment.getContentType(); + } + + /** + * This is used to acquire the content disposition for the part. + * The content disposition contains the Content-Disposition header + * details sent with the part in the multipart request body. + * + * @return value of the header mapped to the specified name + */ + public ContentDisposition getDisposition() { + return segment.getDisposition(); + } + + /** + * This is used to acquire the header value for the specified + * header name. Providing the header values through this method + * ensures any special processing for a know content type can be + * handled by an application. + * + * @param name the name of the header to get the value for + * + * @return value of the header mapped to the specified name + */ + public String getHeader(String name) { + return segment.getValue(name); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/ByteConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/ByteConsumer.java new file mode 100644 index 0000000..886434d --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/ByteConsumer.java @@ -0,0 +1,64 @@ +/* + * ByteConsumer.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>ByteConsumer</code> object is used to consume and process + * bytes from a cursor. This is used to consume bytes from a pipeline + * and process the content in order to produce a valid HTTP message. + * Using a consumer allows the server to gather and process the data + * from the stream bit by bit without blocking. + * <p> + * A consumer has completed its task when it has either exhausted its + * stream, or when it has consume a terminal token. For instance a + * consumer for a HTTP header will have two <code>CRLF</code> bytes + * tokens to identify the end of the header, once this has been read + * any excess bytes are reset on the cursor and it has finished. + * + * @author Niall Gallagher + * + * @see org.simpleframework.transport.ByteCursor + */ +public interface ByteConsumer { + + /** + * This method is used to consume bytes from the provided cursor. + * Consuming of bytes from the cursor should be done in such a + * way that it does not block. So typically only the number of + * ready bytes in the <code>ByteCursor</code> object should be + * read. If there are no ready bytes then this method return. + * + * @param cursor used to consume the bytes from the cursor + */ + void consume(ByteCursor cursor) throws IOException; + + /** + * This is used to determine whether the consumer has finished + * reading. The consumer is considered finished if it has read a + * terminal token or if it has exhausted the stream and can not + * read any more. Once finished the consumed bytes can be parsed. + * + * @return true if the consumer has finished reading its content + */ + boolean isFinished(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/ChunkedConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/ChunkedConsumer.java new file mode 100644 index 0000000..41549d6 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/ChunkedConsumer.java @@ -0,0 +1,258 @@ +/* + * ChunkedConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.Buffer; + +/** + * The <code>ChunkedConsumer</code> is reads an decodes a stream + * using the chunked transfer coding. This is used so that any data + * sent in the chunked transfer coding can be decoded. All bytes are + * appended to an internal buffer so that they can be read without + * having to parse the encoding. + * <pre> + * + * length := 0 + * read chunk-size, chunk-extension (if any) and CRLF + * while (chunk-size > 0) { + * read chunk-data and CRLF + * append chunk-data to entity-body + * length := length + chunk-size + * read chunk-size and CRLF + * } + * read entity-header + * while (entity-header not empty) { + * append entity-header to existing header fields + * read entity-header + * } + * + * </pre> + * The above algorithm is taken from RFC 2616 section 19.4.6. This + * coding scheme is used in HTTP pipelines so that dynamic content, + * that is, content with which a length cannot be determined does + * not require a connection close to delimit the message body. + * + * @author Niall Gallagher + */ +public class ChunkedConsumer extends UpdateConsumer { + + /** + * This is used to create the internal buffer for the body. + */ + private Allocator allocator; + + /** + * This is the internal buffer used to capture the body read. + */ + private Buffer buffer; + + /** + * This is used to determine whether a full chunk has been read. + */ + private boolean terminal; + + /** + * This is used to determine if the zero length chunk was read. + */ + private boolean last; + + /** + * This is used to accumulate the bytes of the chunk size line. + */ + private byte line[]; + + /** + * This is the number of bytes appended to the line buffer. + */ + private int count; + + /** + * This is the number of bytes left in the current chunk. + */ + private int chunk; + + /** + * Constructor for the <code>ChunkedConsumer</code> object. This + * is used to create a consumer that reads chunked encoded data and + * appended that data in decoded form to an internal buffer so that + * it can be read in a clean decoded fromat. + * + * @param allocator this is used to allocate the internal buffer + */ + public ChunkedConsumer(Allocator allocator) { + this(allocator, 1024); + } + + /** + * Constructor for the <code>ChunkedConsumer</code> object. This + * is used to create a consumer that reads chunked encoded data and + * appended that data in decoded form to an internal buffer so that + * it can be read in a clean decoded fromat. + * + * @param allocator this is used to allocate the internal buffer + * @param chunk this is the maximum size line allowed + */ + private ChunkedConsumer(Allocator allocator, int chunk) { + this.line = new byte[chunk]; + this.allocator = allocator; + } + + /** + * This is used to acquire the body that has been consumed. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Attachment</code> objects. + * Each part can then be read as an individual message. + * + * @return the body that has been consumed by this instance + */ + public Body getBody() { + return new BufferBody(buffer); + } + + /** + * This method is used to append the contents of the array to the + * internal buffer. The appended bytes can be acquired from the + * internal buffer using an <code>InputStream</code>, or the text + * of the appended bytes can be acquired by encoding the bytes. + * + * @param array this is the array of bytes to be appended + * @param off this is the start offset in the array to read from + * @param len this is the number of bytes to write to the buffer + */ + private void append(byte[] array, int off, int len) throws IOException { + if(buffer == null) { + buffer = allocator.allocate(); + } + buffer.append(array, off, len); + } + + /** + * This is used to process the bytes that have been read from the + * cursor. This will keep reading bytes from the stream until such + * time as the zero length chunk has been read from the stream. If + * the zero length chunk is encountered then the overflow count is + * returned so it can be used to reset the cursor. + * + * @param array this is a chunk read from the cursor + * @param off this is the offset within the array the chunk starts + * @param size this is the number of bytes within the array + * + * @return this returns the number of bytes overflow that is read + */ + @Override + protected int update(byte[] array, int off, int size) throws IOException { + int mark = off + size; + + while(off < mark){ + if(terminal || last) { + while(off < mark) { + if(array[off++] == '\n') { // CR[LF] + if(last) { // 0; CRLFCR[LF] + finished = true; + return mark - off; + } + terminal = false; + break; + } + } + } else if(chunk == 0) { + while(chunk == 0) { + if(off >= mark) { + break; + } else if(array[off++] == '\n') { // CR[LF] + parse(); + + if(chunk == 0) { // 0; CR[LF]CRLF + last = true; + break; + } + } else { + line[count++] = array[off-1]; + } + } + } else { + int write = Math.min(mark - off, chunk); + + append(array, off, write); + chunk -= write; + off += write; + + if(chunk == 0) { // []CRLF + terminal = true; + } + } + } + return 0; + } + + /** + * This method is used to convert the size in hexidecimal to a + * decimal <code>int</code>. This will use the specified number + * of bytes from the internal buffer and parse each character + * read as a hexidecimal character. This stops interpreting the + * size line when a non-hexidecimal character is encountered. + */ + private void parse() throws IOException { + int off = 0; + + while(off < count) { + int octet = toDecimal(line[off]); + + if(octet < 0){ + if(off < 1) { + throw new IOException("Invalid chunk size line"); + } + break; + } + chunk <<= 4; + chunk ^= octet; + off++; + } + count = 0; + } + + /** + * This performs a conversion from a character to an integer. If + * the character given, as a <code>byte</code>, is a hexidecimal + * char this will convert it into its integer equivelant. So a + * char of <code>A</code> is converted into <code>10</code>. + * + * @param octet this is an ISO 8869-1 hexidecimal character + * + * @return returns the hex character into its decinal value + */ + private int toDecimal(byte octet){ + if(octet >= 'A' && octet <= 'Z') { + return (octet - 'A') + 10; + } + if(octet >= '0' && octet <= '9') { + return octet - '0'; + } + if(octet >= 'a' && octet <= 'f') { + return (octet - 'a') + 10; + } + return -1; + } +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/ConsumerFactory.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/ConsumerFactory.java new file mode 100644 index 0000000..b3e5dc0 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/ConsumerFactory.java @@ -0,0 +1,201 @@ +/* + * ConsumerFactory.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import static org.simpleframework.http.Protocol.BOUNDARY; +import static org.simpleframework.http.Protocol.CHUNKED; +import static org.simpleframework.http.Protocol.MULTIPART; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.http.ContentType; + +/** + * The <code>ConsumerFactory</code> object is used to create a factory + * for creating consumers. This allows the request to determine the + * type of content sent and allows consumption of the request body in + * a the manner specified by the HTTP header. This will allow multipart + * and chunked content to be consumed from the pipeline. + * + * @author Niall Gallagher + */ +class ConsumerFactory { + + /** + * This is used to allocate the memory associated with the body. + */ + protected Allocator allocator; + + /** + * This is the header associated with the request body consumed. + */ + protected Segment segment; + + /** + * Constructor for the <code>ConsumerFactory</code> object. This + * will create a factory that makes use of the HTTP header in order + * to determine the type of the body that is to be consumed. + * + * @param allocator this is the allocator used to allocate memory + * @param segment this is the HTTP header used to determine type + */ + public ConsumerFactory(Allocator allocator, Segment segment) { + this.allocator = allocator; + this.segment = segment; + } + + /** + * This method is used to create a body consumer to read the body + * from the pipeline. This will examine the HTTP header associated + * with the body to determine how to consume the data. This will + * provide an empty consumer if no specific delimiter was provided. + * + * @return this returns the consumer used to consume the body + */ + public BodyConsumer getInstance() { + long length = getContentLength(); + + if(length < 0) { + return getInstance(8192); + } + return getInstance(length); + } + + /** + * This method is used to create a body consumer to read the body + * from the pipeline. This will examine the HTTP header associated + * with the body to determine how to consume the data. This will + * provide an empty consumer if no specific delimiter was provided. + * + * @param length this is the length of the body to be consumed + * + * @return this returns the consumer used to consume the body + */ + public BodyConsumer getInstance(long length) { + byte[] boundary = getBoundary(segment); + + if(isUpload(segment)) { + return new FileUploadConsumer(allocator, boundary, length); + } + if(isChunked(segment)) { + return new ChunkedConsumer(allocator); + } + if(isFixed(segment)) { + return new FixedLengthConsumer(allocator, length); + } + return new EmptyConsumer(); + } + + /** + * This is used to extract information from the HTTP header that + * can be used to determine the type of the body. This will look + * at the HTTP headers provided to find a specific token which + * enables it to determine how to consume the body. + * + * @param header this is the header associated with the body + * + * @return the boundary for a multipart upload body + */ + protected byte[] getBoundary(Segment header) { + ContentType type = header.getContentType(); + + if(type != null) { + String token = type.getParameter(BOUNDARY); + + if(token != null) { + return token.getBytes(); + } + } + return null; + } + + /** + * This is used to extract information from the HTTP header that + * can be used to determine the type of the body. This will look + * at the HTTP headers provided to find a specific token which + * enables it to determine how to consume the body. + * + * @param segment this is the header associated with the body + * + * @return true if the content type is that of a multipart body + */ + protected boolean isUpload(Segment segment) { + ContentType type = segment.getContentType(); + + if(type != null) { + String token = type.getPrimary(); + + if(token.equals(MULTIPART)) { + return true; + } + } + return false; + } + + /** + * This is used to extract information from the HTTP header that + * can be used to determine the type of the body. This will look + * at the HTTP headers provided to find a specific token which + * enables it to determine how to consume the body. + * + * @param segment this is the header associated with the body + * + * @return true if the body is to be consumed as a chunked body + */ + protected boolean isChunked(Segment segment) { + String encoding = segment.getTransferEncoding(); + + if(encoding != null) { + if(encoding.equals(CHUNKED)) { + return true; + } + } + return false; + } + + /** + * This is used to extract information from the HTTP header that + * can be used to determine the type of the body. This will look + * at the HTTP headers provided to find a specific token which + * enables it to determine how to consume the body. + * + * @param segment this is the header associated with the body + * + * @return true if there was a content length in the header + */ + protected boolean isFixed(Segment segment) { + long length = segment.getContentLength(); + + if(length > 0) { + return true; + } + return false; + } + + /** + * This is a convenience method that can be used to determine + * the length of the message body. This will determine if there + * is a <code>Content-Length</code> header, if it does then the + * length can be determined, if not then this returns -1. + * + * @return the content length, or -1 if it cannot be determined + */ + protected long getContentLength() { + return segment.getContentLength(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/ContentConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/ContentConsumer.java new file mode 100644 index 0000000..76742c2 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/ContentConsumer.java @@ -0,0 +1,226 @@ +/* + * ContentConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.Buffer; +import org.simpleframework.http.Part; +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>ContentConsumer</code> object represents a consumer for + * a multipart body part. This will read the contents of the cursor + * until such time as it reads the terminal boundary token, which is + * used to frame the content. Once the boundary token has been read + * this will add itself as a part to a part list. This part list can + * then be used with the HTTP request to examine and use the part. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.message.PartConsumer + */ +class ContentConsumer extends UpdateConsumer { + + /** + * This represents the start of the boundary token for the body. + */ + private static final byte[] START = { '\r', '\n', '-', '-' }; + + /** + * This is the part list that this part is to be added to. + */ + private PartSeries series; + + /** + * This is used to allocate the internal buffer when required. + */ + private Allocator allocator; + + /** + * Represents the HTTP headers that were provided for the part. + */ + private Segment segment; + + /** + * This is the internal buffer used to house the part body. + */ + private Buffer buffer; + + /** + * Represents the message boundary that terminates the part body. + */ + private byte[] boundary; + + /** + * This is used to determine if the start token had been read. + */ + private int start; + + /** + * This is used to determine how many boundary tokens are read. + */ + private int seek; + + /** + * Constructor for the <code>ContentConsumer</code> object. This + * is used to create a consumer that reads the body of a part in + * a multipart request body. The terminal token must be provided + * so that the end of the part body can be determined. + * + * @param allocator this is used to allocate the internal buffer + * @param segment this represents the headers for the part body + * @param series this is the part list that this body belongs in + * @param boundary this is the message boundary for the body part + */ + public ContentConsumer(Allocator allocator, Segment segment, PartSeries series, byte[] boundary) { + this.allocator = allocator; + this.boundary = boundary; + this.segment = segment; + this.series = series; + } + + /** + * This is used to acquire the body for this HTTP entity. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Part</code> objects. Each + * part can then be read as an individual message. + * + * @return the body provided by the HTTP request message + */ + public Body getBody() { + return new BufferBody(buffer); + } + + /** + * This is used to acquire the part for this HTTP entity. This + * will return a part which can be used to read the content of + * the message, the part created contains the contents of the + * body and the headers associated with it. + * + * @return the part provided by the HTTP request message + */ + public Part getPart() { + return new BufferPart(segment, buffer); + } + + /** + * This method is used to append the contents of the array to the + * internal buffer. The appended bytes can be acquired from the + * internal buffer using an <code>InputStream</code>, or the text + * of the appended bytes can be acquired by encoding the bytes. + * + * @param array this is the array of bytes to be appended + * @param off this is the start offset in the array to read from + * @param len this is the number of bytes to write to the buffer + */ + private void append(byte[] array, int off, int len) throws IOException { + if(buffer == null) { + buffer = allocator.allocate(); + } + buffer.append(array, off, len); + } + + /** + * This is used to push the start and boundary back on to the + * cursor. Pushing the boundary back on to the cursor is required + * to ensure that the next consumer will have valid data to + * read from it. Simply resetting the boundary is not enough as + * this can cause an infinite loop if the connection is bad. + * + * @param cursor this is the cursor used by this consumer + */ + @Override + protected void commit(ByteCursor cursor) throws IOException { + cursor.push(boundary); + cursor.push(START); + } + + /** + * This is used to process the bytes that have been read from the + * cursor. This will search for the boundary token within the body + * of the message part, when it is found this will returns the + * number of bytes that represent the overflow. + * + * @param array this is a chunk read from the cursor + * @param off this is the offset within the array the chunk starts + * @param size this is the number of bytes within the array + * + * @return this returns the number of bytes overflow that is read + */ + @Override + protected int update(byte[] array, int off, int size) throws IOException { + int skip = start + seek; // did we skip previously + int last = off + size; + int next = start; + int mark = off; + + while(off < last) { + if(start == START.length) { // search for boundary + if(array[off++] != boundary[seek++]) { // boundary not found + if(skip > 0) { + append(START, 0, next); // write skipped start + append(boundary, 0, skip - next); // write skipped boundary + } + skip = start = seek = 0; // reset scan position + } + if(seek == boundary.length) { // boundary found + int excess = seek + start; // boundary bytes read + int total = off - mark; // total bytes read + int valid = total - excess; // body bytes read + + finished = true; + + if(valid > 0) { + append(array, mark, valid); + } + Part part = getPart(); + + if(part != null) { + series.addPart(part); + } + return size - total; // remaining excluding boundary + } + } else { + byte octet = array[off++]; // current + + if(octet != START[start++]) { + if(skip > 0) { + append(START, 0, next); // write skipped start + } + skip = start = 0; // reset + + if(octet == START[0]) { // is previous byte the start + start++; + } + } + } + } + int excess = seek + start; // boundary bytes read + int total = off - mark; // total bytes read + int valid = total - excess; // body bytes read + + if(valid > 0) { // can we append processed data + append(array, mark, valid); + } + return 0; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/ContinueDispatcher.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/ContinueDispatcher.java new file mode 100644 index 0000000..56f6472 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/ContinueDispatcher.java @@ -0,0 +1,88 @@ +/* + * ContinueDispatcher.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import static org.simpleframework.http.core.ContainerEvent.DISPATCH_CONTINUE; + +import java.io.IOException; + +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteWriter; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>ContinueDispatcher</code> object is used to send the HTTP + * 100 continue status if required. This is delivered to the client + * to tell the client that the server is willing to accept the request + * body. Once this is sent the transport will likely wait until there + * is a read ready event. + * + * @author Niall Gallagher + */ +class ContinueDispatcher { + + /** + * This is the status code that is sent to prompt the client. + */ + private static final byte[] STATUS = { 'H', 'T','T', 'P', '/','1','.', '1',' ', '1','0','0',' '}; + + /** + * This is the optional description for the expect status code. + */ + private static final byte[] MESSAGE = {'C','o','n','t','i','n','u','e', '\r','\n','\r','\n'}; + + /** + * This is the writer that is used to deliver the continue. + */ + private final ByteWriter writer; + + /** + * This is the trace used to capture a continue response if any. + */ + private final Trace trace; + + /** + * Constructor for the <code>ContinueDispatcher</code> object. This + * will create an object that will deliver the continue status code. + * Because the transport performs an asynchronous write this will + * not block the execution of this method and delay execution. + * + * @param channel this is the channel used to deliver the prompt + */ + public ContinueDispatcher(Channel channel) { + this.writer = channel.getWriter(); + this.trace = channel.getTrace(); + } + + /** + * This will execute the continue if the header contains the + * expectation header. If there is no expectation then this will + * return without sending anything back to the connected client. + * + * @param header this is the header read from the channel + */ + public void execute(Header header) throws IOException { + if(header.isExpectContinue()) { + trace.trace(DISPATCH_CONTINUE); + writer.write(STATUS); + writer.write(MESSAGE); + writer.flush(); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/EmptyConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/EmptyConsumer.java new file mode 100644 index 0000000..9fb8145 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/EmptyConsumer.java @@ -0,0 +1,69 @@ +/* + * EmptyConsumer.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>EmptyConsumer</code> object is used to represent a body + * of zero length. This is the most common body consumer created as + * it represents the body for GET messages that have nothing within + * the body part. + * + * @author Niall Gallagher + */ +public class EmptyConsumer implements BodyConsumer { + + /** + * This is used to acquire the body that has been consumed. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Attachment</code> objects. + * Each part can then be read as an individual message. + * + * @return the body that has been consumed by this instance + */ + public Body getBody() { + return new BufferBody(); + } + + /** + * This method will not consume any bytes from the cursor. This + * ensures that the next byte read from the stream is the first + * character of the next HTTP message within the pipeline. + * + * @param cursor this is the cursor which will not be read from + */ + public void consume(ByteCursor cursor) { + return; + } + + /** + * This will return true immediately. Because the empty consumer + * represents a zero length body and no bytes are read from the + * cursor, this should not be processed and return finished. + * + * @return this will always return true for the zero length body + */ + public boolean isFinished() { + return true; + } +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/EmptyInputStream.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/EmptyInputStream.java new file mode 100644 index 0000000..2d1f9ff --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/EmptyInputStream.java @@ -0,0 +1,44 @@ +/* + * EmptyInputStream.java October 2002 + * + * Copyright (C) 2002, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.InputStream; + +/** + * The <code>EmptyInputStream</code> object provides a stream that + * is immediately empty. Each read method with this input stream + * will return a -1 value indicating that the stream has come to an + * end and no more data can be read from it. + * + * @author Niall Gallagher + */ +class EmptyInputStream extends InputStream { + + /** + * This is used to provide a -1 value when an attempt is made to + * read from the stream. Implementing this method as so also + * ensures that all the other read methods return a -1 value. + * + * @return this returns a -1 when an attempt is made to read + */ + public int read() { + return -1; + } + +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/Entity.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/Entity.java new file mode 100644 index 0000000..6668eeb --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/Entity.java @@ -0,0 +1,75 @@ +/* + * Entity.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import org.simpleframework.transport.Channel; + +/** + * The <code>Entity</code> object is used to represent the HTTP entity + * received from the client. The entity contains a header and body as + * well as the underlying <code>Channel</code> for the connection. If + * there is no body with the entity this will provide an empty body + * object which provides a zero length sequence of bytes. + * + * @author Niall Gallagher + */ +public interface Entity { + + /** + * This is the time in milliseconds when the request was first + * read from the underlying channel. The time represented here + * represents the time collection of this request began. This + * does not necessarily represent the time the bytes arrived on + * the receive buffers as some data may have been buffered. + * + * @return this represents the time the request was ready at + */ + long getTime(); + + /** + * This is used to acquire the body for this HTTP entity. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Part</code> objects. Each + * part can then be read as an individual message. + * + * @return the body provided by the HTTP request message + */ + Body getBody(); + + /** + * This provides the HTTP request header for the entity. This is + * always populated and provides the details sent by the client + * such as the target URI and the query if specified. Also this + * can be used to determine the method and protocol version used. + * + * @return the header provided by the HTTP request message + */ + Header getHeader(); + + /** + * This provides the connected channel for the client. This is + * used to send and receive bytes to and from an transport layer. + * Each channel provided with an entity contains an attribute + * map which contains information about the connection. + * + * @return the connected channel for this HTTP entity + */ + Channel getChannel(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/EntityConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/EntityConsumer.java new file mode 100644 index 0000000..9cf12fb --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/EntityConsumer.java @@ -0,0 +1,184 @@ +/* + * EntityConsumer.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import static org.simpleframework.http.core.ContainerEvent.BODY_FINISHED; +import static org.simpleframework.http.core.ContainerEvent.HEADER_FINISHED; +import static org.simpleframework.http.core.ContainerEvent.READ_BODY; +import static org.simpleframework.http.core.ContainerEvent.READ_HEADER; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteCursor; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>EntityConsumer</code> object is used to consume data + * from a cursor and build a request entity. Each constituent part of + * the entity is consumed from the pipeline and can be acquired from + * this consumer object. The <code>Header</code> and <code>Body</code> + * can be used to extract the individual parts of the entity. + * + * @author Niall Gallagher + */ +public class EntityConsumer implements ByteConsumer { + + /** + * This is used to determine if there a continue is expected. + */ + protected ContinueDispatcher dispatcher; + + /** + * This is used to create a body consumer for the entity. + */ + protected ConsumerFactory factory; + + /** + * This is used to consume the header for the request entity. + */ + protected RequestConsumer header; + + /** + * This is used to consume the body for the request entity. + */ + protected BodyConsumer body; + + /** + * This is used to trace the progress of the request consumption. + */ + protected Trace trace; + + /** + * Constructor for the <code>EntityConsumer</code> object. This + * is used to build an entity from the constituent parts. Once + * all of the parts have been consumed they are available from + * the exposed methods of this consumed instance. + * + * @param allocator this is used to allocate the memory used + * @param channel this is the channel used to send a response + */ + public EntityConsumer(Allocator allocator, Channel channel) { + this.header = new RequestConsumer(); + this.dispatcher = new ContinueDispatcher(channel); + this.factory = new ConsumerFactory(allocator, header); + this.trace = channel.getTrace(); + } + + /** + * This is used to acquire the body for this HTTP entity. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Attachment</code> objects. + * Each part can then be read as an individual message. + * + * @return the body provided by the HTTP request message + */ + public Body getBody() { + return body.getBody(); + } + + /** + * This provides the HTTP request header for the entity. This is + * always populated and provides the details sent by the client + * such as the target URI and the query if specified. Also this + * can be used to determine the method and protocol version used. + * + * @return the header provided by the HTTP request message + */ + public Header getHeader() { + return header; + } + + /** + * This consumes the header and body from the cursor. The header + * is consumed first followed by the body if there is any. There + * is a body of there is a Content-Length or a Transfer-Encoding + * header present. If there is no body then a substitute body + * is given which has an empty input stream. + * + * @param cursor used to consumed the bytes for the entity + */ + public void consume(ByteCursor cursor) throws IOException { + while(cursor.isReady()) { + if(header.isFinished()) { + if(body == null) { + CharSequence sequence = header.getHeader(); + + trace.trace(HEADER_FINISHED, sequence); + body = factory.getInstance(); + } + trace.trace(READ_BODY); + body.consume(cursor); + + if(body.isFinished()) { + trace.trace(BODY_FINISHED); + break; + } + } else { + trace.trace(READ_HEADER); + header.consume(cursor); + } + } + if(header.isFinished()) { + if(body == null) { + CharSequence sequence = header.getHeader(); + + trace.trace(HEADER_FINISHED, sequence); + dispatcher.execute(header); + body = factory.getInstance(); + } + } + } + + /** + * This is determined finished when the body has been consumed. + * If only the header has been consumed then the body will be + * created using the header information, the body is then read + * from the cursor, which may read nothing for an empty body. + * + * @return this returns true if the entity has been built + */ + public boolean isFinished() { + if(header.isFinished()) { + if(body == null) { + CharSequence sequence = header.getHeader(); + + trace.trace(HEADER_FINISHED, sequence); + body = factory.getInstance(); + } + return body.isFinished(); + } + return false; + + } + + /** + * This is used to determine if the header has finished. Exposing + * this method ensures the entity consumer can be used to determine + * if the header for the entity can be consumed before fully + * processing the entity body of the request message. + * + * @return determines if the header has been fully consumed + */ + public boolean isHeaderFinished() { + return header.isFinished(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/FileUploadConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/FileUploadConsumer.java new file mode 100644 index 0000000..8318013 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/FileUploadConsumer.java @@ -0,0 +1,272 @@ +/* + * FileUploadConsumer.java February 2013 + * + * Copyright (C) 2013, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>FileUploadConsumer</code> object is used to consume a + * list of parts encoded in the multipart format. This is can consume + * any number of parts from a cursor. Each part consumed is added to an + * internal part list which can be used to acquire the contents of the + * upload and inspect the headers provided for each uploaded part. To + * ensure that only a fixed number of bytes are consumed this wraps + * the provided cursor with a counter to ensure reads a limited amount. + * + * @author Niall Gallagher + */ +public class FileUploadConsumer implements BodyConsumer { + + /** + * This is used to read and parse the contents of the part series. + */ + private final BodyConsumer consumer; + + /** + * This counts the number of bytes remaining the the part series. + */ + private final AtomicLong count; + + /** + * Constructor for the <code>FileUploadConsumer</code> object. + * This is used to create an object that read a series of parts + * from a fixed length body. When consuming the body this will not + * read any more than the content length from the cursor. + * + * @param allocator this is the allocator used to allocate buffers + * @param boundary this is the boundary that is used by this + * @param length this is the number of bytes for this part series + */ + public FileUploadConsumer(Allocator allocator, byte[] boundary, long length) { + this.consumer = new PartSeriesConsumer(allocator, boundary, length); + this.count = new AtomicLong(length); + } + + /** + * This is used to acquire the body that has been consumed. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Part</code> objects. + * Each part can then be read as an individual message. + * + * @return the body that has been consumed by this instance + */ + public Body getBody() { + return consumer.getBody(); + } + + /** + * This method is used to consume bytes from the provided cursor. + * Consuming of bytes from the cursor should be done in such a + * way that it does not block. So typically only the number of + * ready bytes in the <code>ByteCursor</code> object should be + * read. If there are no ready bytes then this will return. + * + * @param cursor used to consume the bytes from the HTTP pipeline + */ + public void consume(ByteCursor cursor) throws IOException { + ByteCounter counter = new ByteCounter(cursor); + + while(counter.isReady()) { + if(consumer.isFinished()) { + break; + } + consumer.consume(counter); + } + } + + /** + * This is used to determine whether the consumer has finished + * reading. The consumer is considered finished if it has read a + * terminal token or if it has exhausted the stream and can not + * read any more. Once finished the consumed bytes can be parsed. + * + * @return true if the consumer has finished reading its content + */ + public boolean isFinished() { + long remaining = count.get(); + + if(consumer.isFinished()) { + return true; + } + return remaining <= 0; + } + + /** + * The <code>ByteCounter</code> is a wrapper for a cursor that can + * be used to restrict the number of bytes consumed. This will + * count the bytes consumed and ensure that any requested data is + * restricted to a chunk less than or equal to the remaining bytes. + */ + private class ByteCounter implements ByteCursor { + + /** + * This is the cursor that this counter will delegate to. + */ + private final ByteCursor cursor; + + /** + * Constructor for the <code>Counter</code> object. This is used + * to create a special cursor that counts the bytes read and + * limits reads to the remaining bytes left in the part series. + * + * @param cursor this is the cursor that is delegated to + */ + public ByteCounter(ByteCursor cursor) { + this.cursor = cursor; + } + + /** + * Determines whether the cursor is still open. The cursor is + * considered open if there are still bytes to read. If there is + * still bytes buffered and the underlying transport is closed + * then the cursor is still considered open. + * + * @return true if the read method does not return a -1 value + */ + public boolean isOpen() throws IOException { + return cursor.isOpen(); + } + + /** + * Determines whether the cursor is ready for reading. When the + * cursor is ready then it guarantees that some amount of bytes + * can be read from the underlying stream without blocking. + * + * @return true if some data can be read without blocking + */ + public boolean isReady() throws IOException { + long limit = count.get(); + + if(limit > 0) { + return cursor.isReady(); + } + return false; + } + + /** + * Provides the number of bytes that can be read from the stream + * without blocking. This is typically the number of buffered or + * available bytes within the stream. When this reaches zero then + * the cursor may perform a blocking read. + * + * @return the number of bytes that can be read without blocking + */ + public int ready() throws IOException { + int limit = (int)count.get(); + int ready = cursor.ready(); + + if(ready > limit) { + return limit; + } + return ready; + } + + /** + * Reads a block of bytes from the underlying stream. This will + * read up to the requested number of bytes from the underlying + * stream. If there are no ready bytes on the stream this can + * return zero, representing the fact that nothing was read. + * + * @param data this is the array to read the bytes in to + * + * @return this returns the number of bytes read from the stream + */ + public int read(byte[] data) throws IOException { + return read(data, 0, data.length); + } + + /** + * Reads a block of bytes from the underlying stream. This will + * read up to the requested number of bytes from the underlying + * stream. If there are no ready bytes on the stream this can + * return zero, representing the fact that nothing was read. + * + * @param data this is the array to read the bytes in to + * @param off this is the offset to begin writing the bytes to + * @param len this is the number of bytes that are requested + * + * @return this returns the number of bytes read from the stream + */ + public int read(byte[] data, int off, int len) throws IOException { + int limit = (int)count.get(); + int size = Math.min(limit, len); + int chunk = cursor.read(data, off, size); + + if(chunk > 0) { + count.addAndGet(-chunk); + } + return chunk; + } + + /** + * Pushes the provided data on to the cursor. Data pushed on to + * the cursor will be the next data read from the cursor. This + * complements the <code>reset</code> method which will reset + * the cursors position on a stream. Allowing data to be pushed + * on to the cursor allows more flexibility. + * + * @param data this is the data to be pushed on to the cursor + */ + public void push(byte[] data) throws IOException { + push(data, 0, data.length); + } + + /** + * Pushes the provided data on to the cursor. Data pushed on to + * the cursor will be the next data read from the cursor. This + * complements the <code>reset</code> method which will reset + * the cursors position on a stream. Allowing data to be pushed + * on to the cursor allows more flexibility. + * + * @param data this is the data to be pushed on to the cursor + * @param off this is the offset to begin reading the bytes + * @param len this is the number of bytes that are to be used + */ + public void push(byte[] data, int off, int len) throws IOException { + if(len > 0) { + count.addAndGet(len); + } + cursor.push(data, off, len); + } + + /** + * Moves the cursor backward within the stream. This ensures + * that any bytes read from the last read can be pushed back + * in to the stream so that they can be read again. This will + * throw an exception if the reset can not be performed. + * + * @param len this is the number of bytes to reset back + * + * @return this is the number of bytes that have been reset + */ + public int reset(int len) throws IOException { + int reset = cursor.reset(len); + + if(reset > 0) { + count.addAndGet(reset); + } + return reset; + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/FixedLengthConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/FixedLengthConsumer.java new file mode 100644 index 0000000..5358d4b --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/FixedLengthConsumer.java @@ -0,0 +1,128 @@ +/* + * FixedLengthConsumer.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.Buffer; + +/** + * The <code>FixedLengthConsumer</code> object reads a fixed number of + * bytes from a cursor. This is typically used when the Content-Length + * header is used as the body delimiter. In order to determine when + * the full body has been consumed this counts the bytes read. Once + * all the bytes have been read any overflow will be reset. All of the + * bytes read are appended to the internal buffer so they can be read. + * + * @author Niall Gallagher + */ +public class FixedLengthConsumer extends UpdateConsumer { + + /** + * This is the allocator used to allocate the buffer used. + */ + private Allocator allocator; + + /** + * This is the internal buffer used to accumulate the body. + */ + private Buffer buffer; + + /** + * This is the number of bytes to be consumed from the cursor. + */ + private long limit; + + /** + * Constructor for the <code>FixedLengthConsumer</code> object. This + * is used to create a consumer that reads a fixed number of bytes + * from a cursor and accumulates those bytes in an internal buffer + * so that it can be read at a later stage. + * + * @param allocator this is used to allocate the internal buffer + * @param limit this is the number of bytes that are to be read + */ + public FixedLengthConsumer(Allocator allocator, long limit) { + this.allocator = allocator; + this.limit = limit; + } + + + /** + * This is used to acquire the body that has been consumed. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Attachment</code> objects. + * Each part can then be read as an individual message. + * + * @return the body that has been consumed by this instance + */ + public Body getBody() { + return new BufferBody(buffer); + } + + /** + * This method is used to append the contents of the array to the + * internal buffer. The appended bytes can be acquired from the + * internal buffer using an <code>InputStream</code>, or the text + * of the appended bytes can be acquired by encoding the bytes. + * + * @param array this is the array of bytes to be appended + * @param off this is the start offset in the array to read from + * @param len this is the number of bytes to write to the buffer + */ + private void append(byte[] array, int off, int len) throws IOException { + if(buffer == null) { + buffer = allocator.allocate(limit); + } + buffer.append(array, off, len); + } + + /** + * This is used to process the bytes that have been read from the + * cursor. This will count the number of bytes read, once all of + * the bytes that form the body have been read this returns the + * number of bytes that represent the overflow. + * + * @param array this is a chunk read from the cursor + * @param off this is the offset within the array the chunk starts + * @param count this is the number of bytes within the array + * + * @return this returns the number of bytes overflow that is read + */ + @Override + protected int update(byte[] array, int off, int count) throws IOException { + int mark = (int)limit; + + if(count >= limit) { + append(array, off, mark); + finished = true; + limit = 0; + return count - mark; + } + if(count > 0) { + append(array, off, count); + limit -= count; + } + return 0; + } +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/Header.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/Header.java new file mode 100644 index 0000000..4b79716 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/Header.java @@ -0,0 +1,213 @@ +/* + * Header.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.util.List; +import java.util.Locale; + +import org.simpleframework.http.Address; +import org.simpleframework.http.Cookie; +import org.simpleframework.http.Path; +import org.simpleframework.http.Query; + +/** + * This is a <code>Header</code> object that is used to represent a + * basic form for the HTTP request message. This is used to extract + * values such as the request line and header values from the request + * message. Access to header values is done case insensitively. + * <p> + * As well as providing the header values and request line values + * this will also provide convenience methods which enable the user + * to determine the length of the body this message header prefixes. + * + * @author Niall Gallagher + */ +public interface Header extends Segment { + + /** + * This can be used to get the target specified for this HTTP + * request. This corresponds to the URI sent in the request + * line. Typically this will be the path part of the URI, but + * can be the full URI if the request is a proxy request. + * + * @return the target URI that this HTTP request specifies + */ + String getTarget(); + + /** + * This method returns a <code>CharSequence</code> holding the data + * consumed for the request. A character sequence is returned as it + * can provide a much more efficient means of representing the header + * data by just wrapping the consumed byte array. + * + * @return this returns the characters consumed for the header + */ + CharSequence getHeader(); + + /** + * This is used to acquire the address from the request line. + * An address is the full URI including the scheme, domain, + * port and the query parts. This allows various parameters + * to be acquired without having to parse the target. + * + * @return this returns the address of the request line + */ + Address getAddress(); + + /** + * This is used to acquire the path as extracted from the + * the HTTP request URI. The <code>Path</code> object that is + * provided by this method is immutable, it represents the + * normalized path only part from the request URI. + * + * @return this returns the normalized path for the request + */ + Path getPath(); + + /** + * This method is used to acquire the query part from the + * HTTP request URI target. This will return only the values + * that have been extracted from the request URI target. + * + * @return the query associated with the HTTP target URI + */ + Query getQuery(); + + /** + * This can be used to get the HTTP method for this request. The + * HTTP specification RFC 2616 specifies the HTTP request methods + * in section 9, Method Definitions. Typically this will be a + * GET or POST method, but can be any valid alphabetic token. + * + * @return the HTTP method that this request has specified + */ + String getMethod(); + + /** + * This can be used to get the major number from a HTTP version. + * The major version corresponds to the major protocol type, that + * is the 1 of a HTTP/1.1 version string. Typically the major + * type is 1, by can be 0 for HTTP/0.9 clients. + * + * @return the major version number for the HTTP message + */ + int getMajor(); + + /** + * This can be used to get the minor number from a HTTP version. + * The minor version corresponds to the minor protocol type, that + * is the 0 of a HTTP/1.0 version string. This number is typically + * used to determine whether persistent connections are supported. + * + * @return the minor version number for the HTTP message + */ + int getMinor(); + + /** + * This method is used to get a <code>List</code> of the names + * for the headers. This will provide the original names for the + * HTTP headers for the message. Modifications to the provided + * list will not affect the header, the list is a simple copy. + * + * @return this returns a list of the names within the header + */ + List<String> getNames(); + + /** + * This can be used to get the integer of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + int getInteger(String name); + + /** + * This can be used to get the date of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + long getDate(String name); + + /** + * This is used to acquire a cookie usiing the name of that cookie. + * If the cookie exists within the HTTP header then it is returned + * as a <code>Cookie</code> object. Otherwise this method will + * return null. Each cookie object will contain the name, value + * and path of the cookie as well as the optional domain part. + * + * @param name this is the name of the cookie object to acquire + * + * @return this returns a cookie object from the header or null + */ + Cookie getCookie(String name); + + /** + * This is used to acquire all cookies that were sent in the header. + * If any cookies exists within the HTTP header they are returned + * as <code>Cookie</code> objects. Otherwise this method will an + * empty list. Each cookie object will contain the name, value and + * path of the cookie as well as the optional domain part. + * + * @return this returns all cookie objects from the HTTP header + */ + List<Cookie> getCookies(); + + /** + * This is used to acquire the locales from the request header. The + * locales are provided in the <code>Accept-Language</code> header. + * This provides an indication as to the languages that the client + * accepts. It provides the locales in preference order. + * + * @return this returns the locales preferred by the client + */ + List<Locale> getLocales(); + + /** + * This is used to determine if the header represents one that + * requires the HTTP/1.1 continue expectation. If the request + * does require this expectation then it should be send the + * 100 status code which prompts delivery of the message body. + * + * @return this returns true if a continue expectation exists + */ + boolean isExpectContinue(); + + /** + * This method returns a string representing the header that was + * consumed by this consumer. For performance reasons it is better + * to acquire the character sequence representing the header as it + * does not require the allocation on new memory. + * + * @return this returns a string representation of this request + */ + String toString(); +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/HeaderConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/HeaderConsumer.java new file mode 100644 index 0000000..55dab40 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/HeaderConsumer.java @@ -0,0 +1,114 @@ +/* + * HeaderConsumer.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.util.List; + +import org.simpleframework.http.Cookie; + +/** + * The <code>HeaderConsumer</code> object is used to consume a HTTP + * header from the cursor. This extends the segment consumer with + * methods specific to the header. Also this enables session cookies + * to be created using the cookies extracted from the header. + * + * @author Niall Gallagher + */ +public abstract class HeaderConsumer extends SegmentConsumer implements Header { + + /** + * Constructor for the <code>HeaderConsumer</code> object. This + * is used to create a consumer capable of reading a header from + * a provided cursor. All methods of the <code>Header</coder> + * interface are implemented in this object. + */ + protected HeaderConsumer() { + super(); + } + + /** + * This can be used to get the date of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + public long getDate(String name) { + return header.getDate(name); + } + + /** + * This can be used to get the integer of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + public int getInteger(String name) { + return header.getInteger(name); + } + + /** + * This method is used to get a <code>List</code> of the names + * for the headers. This will provide the original names for the + * HTTP headers for the message. Modifications to the provided + * list will not affect the header, the list is a simple copy. + * + * @return this returns a list of the names within the header + */ + public List<String> getNames() { + return header.getNames(); + } + + /** + * This is used to acquire a cookie using the name of that cookie. + * If the cookie exists within the HTTP header then it is returned + * as a <code>Cookie</code> object. Otherwise this method will + * return null. Each cookie object will contain the name, value + * and path of the cookie as well as the optional domain part. + * + * @param name this is the name of the cookie object to acquire + * + * @return this returns a cookie object from the header or null + */ + public Cookie getCookie(String name) { + return header.getCookie(name); + } + + /** + * This is used to acquire all cookies that were sent in the header. + * If any cookies exists within the HTTP header they are returned + * as <code>Cookie</code> objects. Otherwise this method will an + * empty list. Each cookie object will contain the name, value and + * path of the cookie as well as the optional domain part. + * + * @return this returns all cookie objects from the HTTP header + */ + public List<Cookie> getCookies() { + return header.getCookies(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/Message.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/Message.java new file mode 100644 index 0000000..ac01dff --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/Message.java @@ -0,0 +1,273 @@ +/* + * Message.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.util.List; + +import org.simpleframework.http.Cookie; + +/** + * The <code>Message</code> object is used to store an retrieve the + * headers for both a request and response. Headers are stored and + * retrieved in a case insensitive manner according to RFC 2616. + * The message also allows multiple header values to be added to a + * single header name, headers such as Cookie and Set-Cookie can be + * added multiple times with different values. + * + * @author Niall Gallagher + */ +public interface Message { + + /** + * This is used to acquire the names of the of the headers that + * have been set in the response. This can be used to acquire all + * header values by name that have been set within the response. + * If no headers have been set this will return an empty list. + * + * @return a list of strings representing the set header names + */ + List<String> getNames(); + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + void setValue(String name, String value); + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + void setInteger(String name, int value); + + /** + * This is used as a convenience method for adding a header that + * needs to be parsed into a HTTP date string. This will convert + * the date given into a date string defined in RFC 2616 sec 3.3.1. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param date the value constructed as an RFC 1123 date string + */ + void setDate(String name, long date); + + /** + * This can be used to add a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + void addValue(String name, String value); + + /** + * This can be used to add a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getInteger</code> in combination with the get methods. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + void addInteger(String name, int value); + + /** + * This is used as a convenience method for adding a header that + * needs to be parsed into a HTTPdate string. This will convert + * the date given into a date string defined in RFC 2616 sec 3.3.1. + * + * @param name the name of the HTTP message header to be added + * @param date the value constructed as an RFC 1123 date string + */ + void addDate(String name, long date); + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the full string + * representing the named header value. If the named header does + * not exist then this will return a null value. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + String getValue(String name); + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the full string + * representing the named header value. If the named header does + * not exist then this will return a null value. + * + * @param name the HTTP message header to get the value from + * @param index gets the value at the index if there are multiple + * + * @return this returns the value that the HTTP message header + */ + String getValue(String name, int index); + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the integer + * representing the named header value. If the named header does + * not exist then this will return a value of minus one, -1. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + int getInteger(String name); + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the long value + * representing the named header value. If the named header does + * not exist then this will return a value of minus one, -1. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + long getDate(String name); + + /** + * This returns the <code>Cookie</code> object stored under the + * specified name. This is used to retrieve cookies that have been + * set with the <code>setCookie</code> methods. If the cookie does + * not exist under the specified name this will return null. + * + * @param name this is the name of the cookie to be retrieved + * + * @return returns the <code>Cookie</code> by the given name + */ + Cookie getCookie(String name); + + /** + * This returns all <code>Cookie</code> objects stored under the + * specified name. This is used to retrieve cookies that have been + * set with the <code>setCookie</code> methods. If there are no + * cookies then this will return an empty list. + * + * @return returns all the <code>Cookie</code> in the response + */ + List<Cookie> getCookies(); + + /** + * The <code>setCookie</code> method is used to set a cookie value + * with the cookie name. This will add a cookie to the response + * stored under the name of the cookie, when this is committed it + * will be added as a Set-Cookie header to the resulting response. + * This is a convenience method that avoids cookie creation. + * + * @param name this is the cookie to be added to the response + * @param value this is the cookie value that is to be used + * + * @return returns the cookie that has been set in the response + */ + Cookie setCookie(String name, String value); + + /** + * The <code>setCookie</code> method is used to set a cookie value + * with the cookie name. This will add a cookie to the response + * stored under the name of the cookie, when this is committed it + * will be added as a Set-Cookie header to the resulting response. + * + * @param cookie this is the cookie to be added to the response + * + * @return returns the cookie that has been set in the response + */ + Cookie setCookie(Cookie cookie); + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benefits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearance. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has highest preference. + * + * @param name the name of the headers that are to be retrieved + * + * @return ordered list of tokens extracted from the header(s) + */ + List<String> getValues(String name); + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benefits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearance. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has highest preference. + * + * @param list this is the list of individual header values + * + * @return ordered list of tokens extracted from the header(s) + */ + List<String> getValues(List<String> list); + + /** + * This is used to acquire all the individual header values from + * the message. The header values provided by this are unparsed + * and represent the actual string values that have been added to + * the message keyed by a given header name. + * + * @param name the name of the header to get the values for + * + * @return this returns a list of the values for the header name + */ + List<String> getAll(String name); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/MessageHeader.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/MessageHeader.java new file mode 100644 index 0000000..b809efe --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/MessageHeader.java @@ -0,0 +1,477 @@ +/* + * Message.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.util.LinkedList; +import java.util.List; + +import org.simpleframework.common.KeyMap; +import org.simpleframework.http.Cookie; +import org.simpleframework.http.parse.DateParser; +import org.simpleframework.http.parse.ValueParser; + +/** + * The <code>Message</code> object is used to store an retrieve the + * headers for both a request and response. Headers are stored and + * retrieved in a case insensitive manner according to RFC 2616. + * The message also allows multiple header values to be added to a + * single header name, headers such as Cookie and Set-Cookie can be + * added multiple times with different values. + * + * @author Niall Gallagher + */ +public class MessageHeader implements Message { + + /** + * This is used to store the cookies added to the HTTP header. + */ + private final KeyMap<Cookie> cookies; + + /** + * This is used to store multiple header values for a name. + */ + private final KeyMap<Series> values; + + /** + * This is used to store the individual names for the header. + */ + private final KeyMap<String> names; + + /** + * This is used to parse all date headers added to the message. + */ + private final DateParser parser; + + /** + * Constructor for the <code>Message</code> object. This is used + * to create a case insensitive means for storing HTTP header + * names and values. Dates can also be added to message as a + * long value and is converted to RFC 1123 compliant date string. + */ + public MessageHeader() { + this.cookies = new KeyMap<Cookie>(); + this.values = new KeyMap<Series>(); + this.names = new KeyMap<String>(); + this.parser = new DateParser(); + } + + /** + * This is used to acquire the names of the of the headers that + * have been set in the response. This can be used to acquire all + * header values by name that have been set within the response. + * If no headers have been set this will return an empty list. + * + * @return a list of strings representing the set header names + */ + public List<String> getNames() { + return names.getValues(); + } + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void setValue(String name, String value) { + List<String> list = getAll(name); + + if(value == null) { + String token = name.toLowerCase(); + + values.remove(token); + names.remove(token); + } else { + list.clear(); + list.add(value); + } + } + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void setInteger(String name, int value) { + setValue(name, String.valueOf(value)); + } + + /** + * This can be used to set a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void setLong(String name, long value) { + setValue(name, String.valueOf(value)); + } + + /** + * This is used as a convenience method for adding a header that + * needs to be parsed into a HTTP date string. This will convert + * the date given into a date string defined in RFC 2616 sec 3.3.1. + * This will perform a <code>remove</code> using the issued header + * name before the header value is set. + * + * @param name the name of the HTTP message header to be added + * @param date the value constructed as an RFC 1123 date string + */ + public void setDate(String name, long date) { + setValue(name, parser.convert(date)); + } + + /** + * This can be used to add a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getValue</code> in combination with the get methods. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void addValue(String name, String value) { + List<String> list = getAll(name); + + if(value != null) { + list.add(value); + } + } + + /** + * This can be used to add a HTTP message header to this object. + * The name and value of the HTTP message header will be used to + * create a HTTP message header object which can be retrieved using + * the <code>getInteger</code> in combination with the get methods. + * + * @param name the name of the HTTP message header to be added + * @param value the value the HTTP message header will have + */ + public void addInteger(String name, int value) { + addValue(name, String.valueOf(value)); + } + + /** + * This is used as a convenience method for adding a header that + * needs to be parsed into a HTTPdate string. This will convert + * the date given into a date string defined in RFC 2616 sec 3.3.1. + * + * @param name the name of the HTTP message header to be added + * @param date the value constructed as an RFC 1123 date string + */ + public void addDate(String name, long date) { + addValue(name, parser.convert(date)); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the full string + * representing the named header value. If the named header does + * not exist then this will return a null value. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name) { + return getValue(name, 0); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the full string + * representing the named header value. If the named header does + * not exist then this will return a null value. + * + * @param name the HTTP message header to get the value from + * @param index this is the index to get the value from + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name, int index) { + List<String> list = getAll(name); + + if(list.size() > index) { + return list.get(index); + } + return null; + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the integer + * representing the named header value. If the named header does + * not exist then this will return a value of minus one, -1. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public int getInteger(String name) { + String value = getValue(name); + + if(value == null) { + return -1; + } + return Integer.parseInt(value); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the long + * representing the named header value. If the named header does + * not exist then this will return a value of minus one, -1. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public long getLong(String name) { + String value = getValue(name); + + if(value == null) { + return -1L; + } + return Long.parseLong(value); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. This will return the long value + * representing the named header value. If the named header does + * not exist then this will return a value of minus one, -1. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public long getDate(String name) { + String value = getValue(name); + + if(value == null) { + return -1; + } + return parser.convert(value); + } + + /** + * This returns the <code>Cookie</code> object stored under the + * specified name. This is used to retrieve cookies that have been + * set with the <code>setCookie</code> methods. If the cookie does + * not exist under the specified name this will return null. + * + * @param name this is the name of the cookie to be retrieved + * + * @return returns the <code>Cookie</code> by the given name + */ + public Cookie getCookie(String name) { + return cookies.get(name); + } + + /** + * This returns all <code>Cookie</code> objects stored under the + * specified name. This is used to retrieve cookies that have been + * set with the <code>setCookie</code> methods. If there are no + * cookies then this will return an empty list. + * + * @return returns all the <code>Cookie</code> in the response + */ + public List<Cookie> getCookies() { + return cookies.getValues(); + } + + /** + * The <code>setCookie</code> method is used to set a cookie value + * with the cookie name. This will add a cookie to the response + * stored under the name of the cookie, when this is committed it + * will be added as a Set-Cookie header to the resulting response. + * This is a convenience method that avoids cookie creation. + * + * @param name this is the cookie to be added to the response + * @param value this is the cookie value that is to be used + * + * @return returns the cookie that has been set in the response + */ + public Cookie setCookie(String name, String value) { + return setCookie(new Cookie(name, value, true)); + } + + /** + * The <code>setCookie</code> method is used to set a cookie value + * with the cookie name. This will add a cookie to the response + * stored under the name of the cookie, when this is committed it + * will be added as a Set-Cookie header to the resulting response. + * + * @param cookie this is the cookie to be added to the response + * + * @return returns the cookie that has been set in the response + */ + public Cookie setCookie(Cookie cookie) { + String name = cookie.getName(); + + if(name != null) { + cookies.put(name, cookie); + } + return cookie; + } + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benefits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearance. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has highest preference. + * + * @param name the name of the headers that are to be retrieved + * + * @return ordered list of tokens extracted from the header(s) + */ + public List<String> getValues(String name) { + return getValues(getAll(name)); + } + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benefits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearance. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has highest preference. + * + * @param list this is the list of individual header values + * + * @return ordered list of tokens extracted from the header(s) + */ + public List<String> getValues(List<String> list) { + return new ValueParser(list).list(); + } + + /** + * This is used to acquire all the individual header values from + * the message. The header values provided by this are unparsed + * and represent the actual string values that have been added to + * the message keyed by a given header name. + * + * @param name the name of the header to get the values for + * + * @return this returns a list of the values for the header name + */ + public List<String> getAll(String name) { + String token = name.toLowerCase(); + Series series = values.get(token); + + if(series == null) { + return getAll(name, token); + } + return series.getValues(); + } + + /** + * This is used to acquire all the individual header values from + * the message. The header values provided by this are unparsed + * and represent the actual string values that have been added to + * the message keyed by a given header name. + * + * @param name the name of the header to get the values for + * @param token this provides a lower case version of the header + * + * @return this returns a list of the values for the header name + */ + private List<String> getAll(String name, String token) { + Series series = new Series(); + String value = names.get(token); + + if(value == null) { + names.put(token, name); + } + values.put(token, series); + + return series.getValues(); + } + + /** + * The <code>Series</code> object is used to represent a list of + * HTTP header value for a given name. It allows multiple values + * to exist for a given header, such as the Cookie header. Most + * entries will contain a single value. + */ + private class Series { + + /** + * Contains the header values that belong to the entry name. + */ + private List<String> value; + + /** + * Constructor for the <code>Entry</code> object. The entry is + * created using the name of the HTTP header. Values can be + * added to the entry list in order to build up the header. + */ + public Series() { + this.value = new LinkedList<String>(); + } + + /** + * This returns the list of header values associated with the + * header name. Each value is added as an individual header + * prefixed by the header name and a semicolon character. + * + * @return this returns the list of values for the header + */ + public List<String> getValues() { + return value; + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/PartBodyConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartBodyConsumer.java new file mode 100644 index 0000000..b30f28f --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartBodyConsumer.java @@ -0,0 +1,129 @@ +/* + * PartBodyConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>PartBodyConsumer</code> object is used to consume a part + * the contents of a multipart body. This will consume the part and + * add it to a part list, once the part has been consumed and added + * to the part list a terminal token is consumed, which is a carriage + * return and line feed. + * + * @author Niall Gallagher + */ +class PartBodyConsumer implements BodyConsumer { + + /** + * This is the token that is consumed after the content body. + */ + private static final byte[] LINE = { '\r', '\n' }; + + /** + * This is used to consume the content from the multipart upload. + */ + private ContentConsumer content; + + /** + * This is used to consume the final terminal token from the part. + */ + private ByteConsumer token; + + /** + * Constructor for the <code>PartBodyConsumer</code> object. This + * is used to create a consumer that reads the body of a part in + * a multipart request body. The terminal token must be provided + * so that the end of the part body can be determined. + * + * @param allocator this is used to allocate the internal buffer + * @param segment this represents the headers for the part body + * @param boundary this is the message boundary for the body part + */ + public PartBodyConsumer(Allocator allocator, Segment segment, byte[] boundary) { + this(allocator, segment, new PartData(), boundary); + } + + /** + * Constructor for the <code>PartBodyConsumer</code> object. This + * is used to create a consumer that reads the body of a part in + * a multipart request body. The terminal token must be provided + * so that the end of the part body can be determined. + * + * @param allocator this is used to allocate the internal buffer + * @param segment this represents the headers for the part body + * @param series this is the part list that this body belongs in + * @param boundary this is the message boundary for the body part + */ + public PartBodyConsumer(Allocator allocator, Segment segment, PartSeries series, byte[] boundary) { + this.content = new ContentConsumer(allocator, segment, series, boundary); + this.token = new TokenConsumer(allocator, LINE); + } + + /** + * This is used to acquire the body that has been consumed. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Attachment</code> objects. + * Each part can then be read as an individual message. + * + * @return the body that has been consumed by this instance + */ + public Body getBody() { + return content.getBody(); + } + + /** + * This is used to consume the part body from the cursor. This + * initially reads the body of the part, which represents the + * actual payload exposed via the <code>Part</code> interface + * once the payload has been consumed the terminal is consumed. + * + * @param cursor this is the cursor to consume the body from + */ + public void consume(ByteCursor cursor) throws IOException { + while(cursor.isReady()) { + if(content.isFinished()) { + if(token.isFinished()) { + break; + } + token.consume(cursor); + } else { + content.consume(cursor); + } + } + } + + /** + * This is used to determine whether the part body has been read + * from the cursor successfully. In order to determine if all of + * the bytes have been read successfully this will check to see + * of the terminal token had been consumed. + * + * @return true if the part body and terminal have been read + */ + public boolean isFinished() { + return token.isFinished(); + } +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/PartConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartConsumer.java new file mode 100644 index 0000000..cc54558 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartConsumer.java @@ -0,0 +1,135 @@ +/* + * PartConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>PartConsumer</code> object is used to consume a part + * from a part list. A part consists of a header and a body, which + * can be either a simple chunk of data or another part list. This + * must be able to cope with either a simple body or a part list. + * + * @author Niall Gallagher + */ +class PartConsumer implements ByteConsumer { + + /** + * This is used to consume the header message of the part. + */ + private SegmentConsumer header; + + /** + * This is used to consume the body data from the part. + */ + private BodyConsumer body; + + /** + * This is used to determine what type the body data is. + */ + private PartFactory factory; + + /** + * This is used to add the consumed parts to when finished. + */ + private PartSeries series; + + /** + * This is the current consumer used to read from the cursor. + */ + private ByteConsumer current; + + /** + * This is the terminal token that ends the part payload. + */ + private byte[] terminal; + + /** + * Constructor for the <code>PartConsumer</code> object. This is + * used to create a consumer used to read the contents of a part + * and the boundary that terminates the content. Any parts that + * are created by this are added to the provided part list. + * + * @param allocator this is the allocator used to creat buffers + * @param series this is the part list used to store the parts + * @param terminal this is the terminal token for the part + * @param length this is the length of the parent part series + */ + public PartConsumer(Allocator allocator, PartSeries series, byte[] terminal, long length) { + this.header = new PartHeaderConsumer(allocator); + this.factory = new PartFactory(allocator, header, length); + this.terminal = terminal; + this.current = header; + this.series = series; + } + + /** + * This is used to create a new body consumer used to consume the + * part body from for the list. This will ensure that the part + * data is created based on the part header consumed. The types + * of part supported are part lists and part body. + * + * @return this returns a consumed for the part content + */ + private BodyConsumer getConsumer() { + return factory.getInstance(series, terminal); + } + + /** + * This is used to consume the part body from the cursor. This + * initially reads the body of the part, which represents the + * actual payload exposed via the <code>Part</code> interface + * once the payload has been consumed the terminal is consumed. + * + * @param cursor this is the cursor to consume the body from + */ + public void consume(ByteCursor cursor) throws IOException { + while(cursor.isReady()) { + if(header.isFinished()) { + if(body == null) { + body = getConsumer(); + current = body; + } else { + if(body.isFinished()) + break; + } + } + current.consume(cursor); + } + } + + /** + * This is used to determine whether the part body has been read + * from the cursor successfully. In order to determine if all of + * the bytes have been read successfully this will check to see + * of the terminal token had been consumed. + * + * @return true if the part body and terminal have been read + */ + public boolean isFinished() { + if(body != null) { + return body.isFinished(); + } + return false; + } +} + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/PartData.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartData.java new file mode 100644 index 0000000..cf7a90a --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartData.java @@ -0,0 +1,101 @@ +/* + * PartData.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.util.ArrayList; +import java.util.List; + +import org.simpleframework.common.KeyMap; +import org.simpleframework.http.Part; + +/** + * The <code>PartData</code> object represents an ordered list of + * parts that were uploaded within a HTTP entity body. This allows + * the parts to be iterated over, or if required accessed by name. + * In order to access the <code>Part</code> object by name it must + * have had a name within the Content-Disposition header. + * + * @author Niall Gallagher + */ +class PartData implements PartSeries { + + /** + * This is the key map that is used to store the part objects. + */ + private final KeyMap<Part> map; + + /** + * This is the list of attachments for this part list object. + */ + private final List<Part> list; + + /** + * Constructor for the <code>PartData</code> object. This is used + * to create an order list of parts that is used by the request + * to access the individual parts uploaded with a HTTP body. + */ + public PartData() { + this.list = new ArrayList<Part>(); + this.map = new KeyMap<Part>(); + } + + /** + * This is used to acquire the attachments associated with this + * list. If no parts have been collected by this list then it + * will return an empty list. The order of the parts in the list + * are the insertion order for consistency. + * + * @return this returns the parts collected in iteration order + */ + public List<Part> getParts() { + return list; + } + + /** + * This is used to add a part to the list. The order the parts are + * added to the list is the iteration order. If the part has a name + * that is not null then it is added to an internal map using that + * name. This allows it to be accesses by name at a later time. + * + * @param part this is the part that is to be added to the list + * + * @return returns true if the list has changed due to the add + */ + public boolean addPart(Part part) { + String name = part.getName(); + + if(name != null) { + map.put(name, part); + } + return list.add(part); + } + + /** + * This method is used to acquire a <code>Part</code> from the list + * using a known name for the part. This is a convenient way to + * access a part when the name for the part is known. + * + * @param name this is the name of the part to acquire + * + * @return the named part or null if the part does not exist + */ + public Part getPart(String name) { + return map.get(name); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/PartEntryConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartEntryConsumer.java new file mode 100644 index 0000000..8e1a38c --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartEntryConsumer.java @@ -0,0 +1,112 @@ +/* + * PartEntryConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>PartEntryConsumer</code> object is used to consume each + * part from the part list. This is combines the task of consuming + * the part, which consists of a header and a body, and a boundary + * which identifies the end of the message content. + * + * @author Niall Gallagher + */ +class PartEntryConsumer implements ByteConsumer { + + /** + * This is used to consume the boundary at the end of a part. + */ + private final BoundaryConsumer boundary; + + /** + * This is used to consume the actual part from the list. + */ + private final ByteConsumer consumer; + + /** + * Constructor for the <code>PartEntryConsumer</code> object. This + * is used to create a consumer that will read the message part + * and the boundary that terminates the part. All contents that + * are read are appended to an internal buffer. + * + * @param allocator this is the allocator used for the buffer + * @param series this is the list used to accumulate the parts + * @param terminal this is the terminal token for the part list + * @param length this is the length of the parent part series + */ + public PartEntryConsumer(Allocator allocator, PartSeries series, byte[] terminal, long length) { + this.consumer = new PartConsumer(allocator, series, terminal, length); + this.boundary = new BoundaryConsumer(allocator, terminal); + } + + /** + * This is used to consume the part body from the cursor. This + * initially reads the body of the part, which represents the + * actual content exposed via the <code>Part</code> interface + * once the content has been consumed the terminal is consumed. + * + * @param cursor this is the cursor to consume the body from + */ + public void consume(ByteCursor cursor) throws IOException { + while(cursor.isReady()) { + if(!boundary.isFinished()) { + boundary.consume(cursor); + } else { + if(consumer.isFinished()) { + break; + } + if(boundary.isEnd()) { + break; + } + consumer.consume(cursor); + } + } + } + + /** + * This is used to determine whether the part body has been read + * from the cursor successfully. In order to determine if all of + * the bytes have been read successfully this will check to see + * of the terminal token had been consumed. + * + * @return true if the part body and terminal have been read + */ + public boolean isFinished() { + if(boundary.isEnd()) { + return true; + } + return consumer.isFinished(); + } + + /** + * This is used to determine whether the terminal token read is + * the final terminal token. The final terminal token is a + * normal terminal token, however it ends with two hyphens and + * a carriage return line feed, this ends the part list. + * + * @return true if this was the last part within the list + */ + public boolean isEnd() { + return boundary.isEnd(); + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/PartEntryFactory.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartEntryFactory.java new file mode 100644 index 0000000..aba5738 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartEntryFactory.java @@ -0,0 +1,84 @@ +/* + * PartEntryFactory.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import org.simpleframework.common.buffer.Allocator; + +/** + * This <code>PartEntryFactory</code> object provides a factory for + * creating part entry consumers. The part entry consumers created + * read individual entries from a list of parts within a stream. + * This is basically a convenience factory for the list consumer. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.message.PartSeriesConsumer + */ +class PartEntryFactory { + + /** + * This is used to accumulate all the parts of the upload. + */ + private final PartSeries series; + + /** + * This is used to allocate the buffers used by the entry. + */ + private final Allocator allocator; + + /** + * This is the terminal token used to delimiter the upload. + */ + private final byte[] terminal; + + /** + * This is the length of the parent part series body. + */ + private final long length; + + /** + * Constructor for the <code>PartEntryFactory</code> object. + * This is used to create a factory for entry consumers that + * can be used to read an entry from a part list. + * + * @param allocator this is the allocator used for buffers + * @param series this is the list of parts that are extracted + * @param terminal this is the terminal buffer to be used + * @param length this is the length of the parent part series + */ + public PartEntryFactory(Allocator allocator, PartSeries series, byte[] terminal, long length) { + this.allocator = allocator; + this.terminal = terminal; + this.series = series; + this.length = length; + } + + + /** + * This creates a new part entry consumer that can be used to + * read the next part from the list. The consumer instantiated + * by this factory acquires the allocator, list and boundary + * from the enclosing part list consumer instance. + * + * @return a part entry consumer for this part list consumer + */ + public PartEntryConsumer getInstance() { + return new PartEntryConsumer(allocator, series, terminal, length); + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/PartFactory.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartFactory.java new file mode 100644 index 0000000..f394ba6 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartFactory.java @@ -0,0 +1,78 @@ +/* + * PartFactory.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import org.simpleframework.common.buffer.Allocator; + +/** + * The <code>PartFactory</code> represents a factory for creating the + * consumers that are used to read a multipart upload message. This + * supports two types of consumers for the multipart upload, lists + * and bodies. A part list is basically a collection of parts and or + * part lists. The part type is determined from the part header. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.message.PartSeriesConsumer + * @see org.simpleframework.http.message.PartBodyConsumer + */ +class PartFactory extends ConsumerFactory { + + /** + * This is the overall length of the parent part series. + */ + private final long length; + + /** + * Constructor for the <code>PartFactory</code> object. This is + * used to create a factory using a buffer allocator, which will + * create a buffer for accumulating the entire message body, + * also to ensure the correct part type is created this requires + * the header information for the part. + * + * @param allocator this is used to allocate the internal buffer + * @param header this is used to determine the part type + * @param length this is the length of the parent part series + */ + public PartFactory(Allocator allocator, Segment header, long length) { + super(allocator, header); + this.length = length; + } + + /** + * This method is used to create the consumer given the list and + * boundary for the part. In order to determine the part type + * this will consult the header consumed for the part. Depending + * on whether it is a list or body a suitable consumer is created. + * + * @param series this is the list used to collect the parts + * @param boundary this is the boundary used to terminate the part + * + * @return this will return the consumer for the part body + */ + public BodyConsumer getInstance(PartSeries series, byte[] boundary) { + byte[] terminal = getBoundary(segment); + + if(isUpload(segment)) { + return new PartSeriesConsumer(allocator, series, terminal, length); + } + return new PartBodyConsumer(allocator, segment, series, boundary); + } +} + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/PartHeaderConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartHeaderConsumer.java new file mode 100644 index 0000000..7612d8d --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartHeaderConsumer.java @@ -0,0 +1,85 @@ +/* + * PartHeaderConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.Buffer; + +/** + * The <code>PartHeaderConsumer</code> object is used to consume the + * header for a multipart message. This performs a parse of the + * HTTP headers within the message up to the terminal carriage return + * and line feed token. Once this had been read the contents of the + * header are appended to a buffer so they can be read later. + * + * @author Niall Gallagher + */ +class PartHeaderConsumer extends SegmentConsumer { + + /** + * This is used to allocate the internal buffer for the header. + */ + private Allocator allocator; + + /** + * This is the internal buffer used to store the header. + */ + private Buffer buffer; + + /** + * Constructor for the <code>PartHeaderConsumer</code> object. An + * allocator is required so that the header consumer can create a + * buffer to store the contents of the consumed message. + * + * @param allocator this is the allocator used to create a buffer + */ + public PartHeaderConsumer(Allocator allocator) { + this.allocator = allocator; + } + + /** + * This is used to process the header consumer once all of the + * headers have been read. This will simply parse all of the + * headers and append the consumed bytes to the internal buffer. + * Appending the bytes ensures that the whole upload can be + * put back together as a single byte stream if required. + */ + @Override + protected void process() throws IOException { + headers(); + append(); + } + + /** + * This is used to allocate the internal buffer and append the + * consumed bytes to the buffer. Once the header is added to + * the internal buffer this is finished and the next part of + * the upload can be consumed. + */ + private void append() throws IOException { + if(buffer == null) { + buffer = allocator.allocate(count); + } + buffer.append(array, 0, count); + } +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/PartSeries.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartSeries.java new file mode 100644 index 0000000..c971d97 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartSeries.java @@ -0,0 +1,68 @@ +/* + * PartSeries.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.util.List; + +import org.simpleframework.http.Part; + +/** + * The <code>PartSeries</code> object represents an ordered list of + * parts that were uploaded within a HTTP entity body. This allows + * the parts to be iterated over, or if required accessed by name. + * In order to access the <code>Part</code> object by name it must + * have had a name within the Content-Disposition header. + * + * @author Niall Gallagher + */ +interface PartSeries { + + /** + * This is used to acquire the attachments associated with this + * list. If no parts have been collected by this list then it + * will return an empty list. The order of the parts in the list + * are the insertion order for consistency. + * + * @return this returns the parts collected in iteration order + */ + List<Part> getParts(); + + /** + * This is used to add a part to the list. The order the parts are + * added to the list is the iteration order. If the part has a name + * that is not null then it is added to an internal map using that + * name. This allows it to be accesses by name at a later time. + * + * @param part this is the part that is to be added to the list + * + * @return returns true if the list has changed due to the add + */ + boolean addPart(Part part); + + /** + * This method is used to acquire a <code>Part</code> from the list + * using a known name for the part. This is a convenient way to + * access a part when the name for the part is known. + * + * @param name this is the name of the part to acquire + * + * @return the named part or null if the part does not exist + */ + Part getPart(String name); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/PartSeriesConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartSeriesConsumer.java new file mode 100644 index 0000000..c3a0417 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/PartSeriesConsumer.java @@ -0,0 +1,165 @@ +/* + * PartSeriesConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.BufferAllocator; +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>PartSeriesConsumer</code> object is used to consume a list + * of parts encoded in the multipart format. This is can consume any + * number of parts from a cursor. Each part consumed is added to an + * internal part list which can be used to acquire the contents of the + * upload and inspect the headers provided for each uploaded part. To + * ensure that only a fixed number of bytes are consumed this uses a + * content length for an internal buffer. + * + * @author Niall Gallagher + */ +class PartSeriesConsumer implements BodyConsumer { + + /** + * This is used to consume individual parts from the part list. + */ + private PartEntryConsumer consumer; + + /** + * This is the factory that is used to create the consumers used. + */ + private PartEntryFactory factory; + + /** + * This is used to both allocate and buffer the part list body. + */ + private BufferAllocator buffer; + + /** + * This is used to accumulate all the parts of the upload. + */ + private PartSeries series; + + /** + * Constructor for the <code>PartSeriesConsumer</code> object. This + * will create a consumer that is capable of breaking an upload in + * to individual parts so that they can be accessed and used by + * the receiver of the HTTP request message. + * + * @param allocator this is used to allocate the internal buffer + * @param boundary this is the boundary used for the upload + */ + public PartSeriesConsumer(Allocator allocator, byte[] boundary) { + this(allocator, boundary, 8192); + } + + /** + * Constructor for the <code>PartSeriesConsumer</code> object. This + * will create a consumer that is capable of breaking an upload in + * to individual parts so that they can be accessed and used by + * the receiver of the HTTP request message. + * + * @param allocator this is used to allocate the internal buffer + * @param boundary this is the boundary used for the upload + * @param length this is the number of bytes the upload should be + */ + public PartSeriesConsumer(Allocator allocator, byte[] boundary, long length) { + this(allocator, new PartData(), boundary, length); + } + + /** + * Constructor for the <code>PartSeriesConsumer</code> object. This + * will create a consumer that is capable of breaking an upload in + * to individual parts so that they can be accessed and used by + * the receiver of the HTTP request message. + * + * @param allocator this is used to allocate the internal buffer + * @param boundary this is the boundary used for the upload + * @param series this is the part list used to accumulate the parts + */ + public PartSeriesConsumer(Allocator allocator, PartSeries series, byte[] boundary) { + this(allocator, series, boundary, 8192); + } + + /** + * Constructor for the <code>PartSeriesConsumer</code> object. This + * will create a consumer that is capable of breaking an upload in + * to individual parts so that they can be accessed and used by + * the receiver of the HTTP request message. + * + * @param allocator this is used to allocate the internal buffer + * @param series this is the part list used to accumulate the parts + * @param boundary this is the boundary used for the upload + * @param length this is the number of bytes the upload should be + */ + public PartSeriesConsumer(Allocator allocator, PartSeries series, byte[] boundary, long length) { + this.buffer = new BufferAllocator(allocator, length); + this.consumer = new PartEntryConsumer(buffer, series, boundary, length); + this.factory = new PartEntryFactory(buffer, series, boundary, length); + this.series = series; + } + + /** + * This is used to acquire the body that has been consumed. This + * will return a body which can be used to read the content of + * the message, also if the request is multipart upload then all + * of the parts are provided as <code>Attachment</code> objects. + * Each part can then be read as an individual message. + * + * @return the body that has been consumed by this instance + */ + public Body getBody() { + return new BufferBody(buffer, series); + } + + /** + * This is used to consume the part list from the cursor. This + * initially reads the list of parts, which represents the + * actual content exposed via the <code>PartSeries</code> object, + * once the content has been consumed the terminal is consumed. + * + * @param cursor this is the cursor to consume the list from + */ + public void consume(ByteCursor cursor) throws IOException { + while(cursor.isReady()) { + if(!consumer.isFinished()) { + consumer.consume(cursor); + } else { + if(!consumer.isEnd()) { + consumer = factory.getInstance(); + } else { + break; + } + } + } + } + + /** + * This is used to determine whether the part body has been read + * from the cursor successfully. In order to determine if all of + * the bytes have been read successfully this will check to see + * of the terminal token had been consumed. + * + * @return true if the part body and terminal have been read + */ + public boolean isFinished() { + return consumer.isEnd(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/RequestConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/RequestConsumer.java new file mode 100644 index 0000000..0687271 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/RequestConsumer.java @@ -0,0 +1,457 @@ +/* + * RequestConsumer.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.util.List; + +import org.simpleframework.http.Address; +import org.simpleframework.http.Path; +import org.simpleframework.http.Query; +import org.simpleframework.http.parse.AddressParser; + +/** + * The <code>RequestConsumer</code> object is used to parse the HTTP + * request line followed by the HTTP message headers. This parses the + * request URI such that the query parameters and path are extracted + * and normalized. It performs this using external parsers, which + * will remove and escaped characters and normalize the path segments. + * Finally this exposes the HTTP version used using the major and + * minor numbers sent with the HTTP request. + * + * @author Niall Gallagher + */ +public class RequestConsumer extends HeaderConsumer { + + /** + * This is the address parser used to parse the request URI. + */ + protected AddressParser parser; + + /** + * This is the method token send with the HTTP request header. + */ + protected String method; + + /** + * This represents the raw request URI in an unparsed form. + */ + protected String target; + + /** + * This is the major version number of the HTTP request header. + */ + protected int major; + + /** + * This is the minor version number of the HTTP request header. + */ + protected int minor; + + /** + * Constructor for the <code>RequestConsumer</code> object. This + * is used to create a consumer which can consume a HTTP request + * header and provide the consumed contents via a known interface. + * This also further breaks down the request URI for convenience. + */ + public RequestConsumer() { + super(); + } + + /** + * This can be used to get the target specified for this HTTP + * request. This corresponds to the URI sent in the request + * line. Typically this will be the path part of the URI, but + * can be the full URI if the request is a proxy request. + * + * @return the target URI that this HTTP request specifies + */ + public String getTarget() { + return target; + } + + /** + * This is used to acquire the address from the request line. + * An address is the full URI including the scheme, domain, + * port and the query parts. This allows various parameters + * to be acquired without having to parse the target. + * + * @return this returns the address of the request line + */ + public Address getAddress() { + if(parser == null) { + parser = new AddressParser(target); + } + return parser; + } + + /** + * This method is used to acquire the query part from the + * HTTP request URI target. This will return only the values + * that have been extracted from the request URI target. + * + * @return the query associated with the HTTP target URI + */ + public Query getQuery() { + return getAddress().getQuery(); + } + + /** + * This is used to acquire the path as extracted from the + * the HTTP request URI. The <code>Path</code> object that is + * provided by this method is immutable, it represents the + * normalized path only part from the request URI. + * + * @return this returns the normalized path for the request + */ + public Path getPath() { + return getAddress().getPath(); + } + + /** + * This can be used to get the HTTP method for this request. The + * HTTP specification RFC 2616 specifies the HTTP request methods + * in section 9, Method Definitions. Typically this will be a + * GET or POST method, but can be any valid alphabetic token. + * + * @return the HTTP method that this request has specified + */ + public String getMethod() { + return method; + } + + /** + * This can be used to get the major number from a HTTP version. + * The major version corrosponds to the major protocol type, that + * is the 1 of a HTTP/1.0 version string. Typically the major + * type is 1, by can be 0 for HTTP/0.9 clients. + * + * @return the major version number for the HTTP message + */ + public int getMajor() { + return major; + } + + /** + * This can be used to get the minor number from a HTTP version. + * The minor version corrosponds to the minor protocol type, that + * is the 0 of a HTTP/1.0 version string. This number is typically + * used to determine whether persistent connections are supported. + * + * @return the minor version number for the HTTP message + */ + public int getMinor() { + return minor; + } + + /** + * This can be used to get the date of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + public long getDate(String name) { + return header.getDate(name); + } + + /** + * This can be used to get the integer of the first message header + * that has the specified name. This is a convenience method that + * avoids having to deal with parsing the value of the requested + * HTTP message header. This returns -1 if theres no HTTP header + * value for the specified name. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the date as a long from the header value + */ + public int getInteger(String name) { + return header.getInteger(name); + } + + /** + * This method is used to get a <code>List</code> of the names + * for the headers. This will provide the original names for the + * HTTP headers for the message. Modifications to the provided + * list will not affect the header, the list is a simple copy. + * + * @return this returns a list of the names within the header + */ + public List<String> getNames() { + return header.getNames(); + } + + /** + * This method is invoked after the terminal token has been read. + * It is used to process the consumed data and is typically used to + * parse the input such that it can be used by the subclass for + * some useful puropse. This is called only once by the consumer. + */ + @Override + protected void process() { + method(); + target(); + version(); + end(); + headers(); + } + + /** + * This will parse URI target from the first line of the header + * and store the parsed string internally. The target token is + * used to create an <code>Address</code> object which provides + * all the details of the target including the query part. + */ + private void target() { + Token token = new Token(array, pos, 0); + + while(pos < count){ + if(white(array[pos])){ + pos++; + break; + } + token.size++; + pos++; + } + target = token.toString(); + } + + /** + * This will parse HTTP method from the first line of the header + * and store the parsed string internally. The method is used to + * determine what action to take with the request, it also acts + * as a means to determine the semantics of the request. + */ + private void method() { + Token token = new Token(array, pos, 0); + + while(pos < count){ + if(white(array[pos])){ + pos++; + break; + } + token.size++; + pos++; + } + method = token.toString(); + } + + /** + * This will parse HTTP version from the first line of the header + * and store the parsed string internally. The method is used to + * determine what version of HTTP is being used. Typically this + * will be HTTP/1.1 however HTTP/1.0 must be supported and this + * has different connection semantics with regards to pipelines. + */ + protected void version() { + pos += 5; /* "HTTP/" */ + major(); /* "1" */ + pos++; /* "." */ + minor(); /* "1" */ + } + + /** + * This will parse the header from the current offset and convert + * the bytes found into an int as it parses the digits it comes + * accross. This will cease to parse bytes when it encounters a + * non digit byte or the end of the readable bytes. + */ + private void major() { + while(pos < count){ + if(!digit(array[pos])){ + break; + } + major *= 10; + major += array[pos]; + major -= '0'; + pos++; + } + } + + /** + * This will parse the header from the current offset and convert + * the bytes found into an int as it parses the digits it comes + * accross. This will cease to parse bytes when it encounters a + * non digit byte or the end of the readable bytes. + */ + private void minor() { + while(pos < count){ + if(!digit(array[pos])){ + break; + } + minor *= 10; + minor += array[pos]; + minor -= '0'; + pos++; + } + } + + /** + * This is used to determine if a given ISO-8859-1 byte is a digit + * character, between an ISO-8859-1 0 and 9. If it is, this will + * return true otherwise it returns false. + * + * @param octet this is to be checked to see if it is a digit + * + * @return true if the byte is a digit character, false otherwise + */ + protected boolean digit(byte octet) { + return octet >= '0' && octet <= '9'; + } + + /** + * This method returns a <code>CharSequence</code> holding the data + * consumed for the request. A character sequence is returned as it + * can provide a much more efficient means of representing the header + * data by just wrapping the consumed byte array. + * + * @return this returns the characters consumed for the header + */ + public CharSequence getHeader() { + return new Token(array, 0, count); + } + + /** + * This is used to convert the byte range to a string. This + * will use UTF-8 encoding for the string which is compatible + * with the HTTP default header encoding of ISO-8859-1. + * + * @return the encoded string representing the token + */ + public String toString() { + return getHeader().toString(); + } + + /** + * This is a sequence of characters representing the header data + * consumed. Here the internal byte buffer is simply wrapped so + * that it can be a represented as a <code>CharSequence</code>. + * Wrapping the consumed array in this manner ensures that no + * further memory allocation is required. + */ + private static class Token implements CharSequence { + + /** + * This is the array that contains the header bytes. + */ + public byte[] array; + + /** + * This is the number of bytes to use from the array. + */ + public int size; + + /** + * This is the offset in the array the token begins at. + */ + public int off; + + /** + * Constructor for the <code>ByteSequence</code> object. This + * is used to represent the data that has been consumed by + * the header. It acts as a light weight wrapper for the data + * and avoids having to create new strings for each event. + * + * @param array this is the array representing the header + * @param off the starting offset for the token range + * @param size the number of bytes used for the token + */ + private Token(byte[] array, int off, int size) { + this.array = array; + this.size = size; + this.off = off; + } + + /** + * This returns the length of the header in bytes. The length + * includes the request line and all of the control characters + * including the carriage return and line feed at the end of + * the request header. + * + * @return this returns the number of bytes for the header + */ + public int length() { + return size; + } + + /** + * This is used to acquire the character at the specified index. + * Characters returned from this method are simply the bytes + * casted to a character. This may not convert the character + * correctly and a more sensible method should be used. + * + * @param index the index to extract the character from + * + * @return this returns the character found at the index + */ + public char charAt(int index) { + return (char) array[index]; + } + + /** + * This returns a section of characters within the specified + * range. Acquiring a section in this manner is simply done by + * setting a start and end offset within the internal array. + * + * @param start this is the start index to be used + * @param end this is the end index to be used + * + * @return this returns a new sequence within the original + */ + public CharSequence subSequence(int start, int end) { + return new Token(array, start, end - start); + } + + /** + * This is used to create a string from the header bytes. This + * converts the header bytes to a string using a compatible + * encoding. This may produce different results depending on + * the time it is invoked, as the header consumes more data. + * + * @return this returns an encoded version of the header + */ + public String toString() { + return toString("UTF-8"); + } + + /** + * This is used to create a string from the header bytes. This + * converts the header bytes to a string using a compatible + * encoding. This may produce different results depending on + * the time it is invoked, as the header consumes more data. + * + * @param charset this is the encoding to use for the header + * + * @return this returns an encoded version of the header + */ + public String toString(String charset) { + try { + return new String(array, off, size, charset); + } catch(Exception e) { + return null; + } + } + } +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/Segment.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/Segment.java new file mode 100644 index 0000000..915b231 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/Segment.java @@ -0,0 +1,163 @@ +/* + * Segment.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import org.simpleframework.http.ContentDisposition; +import org.simpleframework.http.ContentType; + +import java.util.List; + +/** + * The <code>Segment</code> object represents a collection of header + * values that is followed by a body. This is used to represent the + * header of a multipart upload part. The raw value of each header + * for the part can be acquired using this interface, also the type + * and the disposition of the body can be determined from this. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.Part + */ +public interface Segment { + + /** + * This method is used to determine the type of a part. Typically + * a part is either a text parameter or a file. If this is true + * then the content represented by the associated part is a file. + * + * @return this returns true if the associated part is a file + */ + boolean isFile(); + + /** + * This method is used to acquire the name of the part. Typically + * this is used when the part represents a text parameter rather + * than a file. However, this can also be used with a file part. + * + * @return this returns the name of the associated part + */ + String getName(); + + /** + * This method is used to acquire the file name of the part. This + * is used when the part represents a text parameter rather than + * a file. However, this can also be used with a file part. + * + * @return this returns the file name of the associated part + */ + String getFileName(); + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma separated list of + * values the value returned is the first value in that list. + * This returns null if there is no HTTP message header. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + String getValue(String name); + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma separated list of + * values the value returned is the first value in that list. + * This returns null if there is no HTTP message header. + * + * @param name the HTTP message header to get the value from + * @param index acquires a specific header value from multiple + * + * @return this returns the value that the HTTP message header + */ + String getValue(String name, int index); + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benefits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearance. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has highest preference. + * + * @param name the name of the headers that are to be retrieved + * + * @return ordered array of tokens extracted from the header(s) + */ + List<String> getValues(String name); + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Type</code> header, if there is then + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content type value if it exists + */ + ContentType getContentType(); + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Disposition</code> header, if there is + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content disposition value if it exists + */ + ContentDisposition getDisposition(); + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Transfer-Encoding</code> header, if there is + * then this will parse that header and return the first token in + * the comma separated list of values, which is the primary value. + * + * @return this returns the transfer encoding value if it exists + */ + String getTransferEncoding(); + + /** + * This is a convenience method that can be used to determine + * the length of the message body. This will determine if there + * is a <code>Content-Length</code> header, if it does then the + * length can be determined, if not then this returns -1. + * + * @return the content length, or -1 if it cannot be determined + */ + long getContentLength(); +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/SegmentConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/SegmentConsumer.java new file mode 100644 index 0000000..5c994ce --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/SegmentConsumer.java @@ -0,0 +1,750 @@ +/* + * SegmentConsumer.java February 2007 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import static org.simpleframework.http.Protocol.ACCEPT_LANGUAGE; +import static org.simpleframework.http.Protocol.CONTENT_DISPOSITION; +import static org.simpleframework.http.Protocol.CONTENT_LENGTH; +import static org.simpleframework.http.Protocol.CONTENT_TYPE; +import static org.simpleframework.http.Protocol.COOKIE; +import static org.simpleframework.http.Protocol.EXPECT; +import static org.simpleframework.http.Protocol.TRANSFER_ENCODING; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import org.simpleframework.http.ContentDisposition; +import org.simpleframework.http.ContentType; +import org.simpleframework.http.Cookie; +import org.simpleframework.http.parse.ContentDispositionParser; +import org.simpleframework.http.parse.ContentTypeParser; +import org.simpleframework.http.parse.CookieParser; +import org.simpleframework.http.parse.LanguageParser; + +/** + * The <code>SegmentConsumer</code> object provides a consumer that is + * used to consume a HTTP header. This will read all headers within a + * HTTP header message until the carriage return line feed empty line + * is encountered. Once all headers are consumed they are available + * using the case insensitive header name. This will remove leading + * and trailing whitespace from the names and values parsed. + * + * @author Niall Gallagher + */ +public class SegmentConsumer extends ArrayConsumer implements Segment { + + /** + * This is the terminal carriage return and line feed end line. + */ + private static final byte[] TERMINAL = { 13, 10, 13, 10 }; + + /** + * This is used to represent the content disposition header. + */ + protected ContentDisposition disposition; + + /** + * This is used to parse the languages accepted in the request. + */ + protected LanguageParser language; + + /** + * This is used to parse the cookie headers that are consumed. + */ + protected CookieParser cookies; + + /** + * This is used to store all consumed headers by the header name. + */ + protected MessageHeader header; + + /** + * This is used to parse the content type header consumed. + */ + protected ContentType type; + + /** + * This represents the transfer encoding value of the body. + */ + protected String encoding; + + /** + * During parsing this is used to store the parsed header name, + */ + protected String name; + + /** + * During parsing this is used to store the parsed header value. + */ + protected String value; + + /** + * This is used to determine if there is a continue expected. + */ + protected boolean expect; + + /** + * Represents the length of the body from the content length. + */ + protected long length; + + /** + * This represents the length limit of the HTTP header cosumed. + */ + protected long limit; + + /** + * This is used to track the read offset within the header. + */ + protected int pos; + + /** + * This is used to track how much of the terminal is read. + */ + protected int scan; + + /** + * Constructor for the <code>SegmentConsumer</code> object. This + * is used to create a segment consumer used to consume and parse + * a HTTP message header. This delegates parsing of headers if + * they represent special headers, like content type or cookies. + */ + public SegmentConsumer() { + this(1048576); + } + + /** + * Constructor for the <code>SegmentConsumer</code> object. This + * is used to create a segment consumer used to consume and parse + * a HTTP message header. This delegates parsing of headers if + * they represent special headers, like content type or cookies. + * + * @param limit this is the length limit for a HTTP header + */ + public SegmentConsumer(int limit) { + this.language = new LanguageParser(); + this.cookies = new CookieParser(); + this.header = new MessageHeader(); + this.limit = limit; + this.length = -1; + } + + /** + * This method is used to determine the type of a part. Typically + * a part is either a text parameter or a file. If this is true + * then the content represented by the associated part is a file. + * + * @return this returns true if the associated part is a file + */ + public boolean isFile() { + if(disposition == null) { + return false; + } + return disposition.isFile(); + } + + /** + * This method is used to acquire the name of the part. Typically + * this is used when the part represents a text parameter rather + * than a file. However, this can also be used with a file part. + * + * @return this returns the name of the associated part + */ + public String getName() { + if(disposition == null) { + return null; + } + return disposition.getName(); + } + + /** + * This method is used to acquire the file name of the part. This + * is used when the part represents a text parameter rather than + * a file. However, this can also be used with a file part. + * + * @return this returns the file name of the associated part + */ + public String getFileName() { + if(disposition == null) { + return null; + } + return disposition.getFileName(); + } + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Type</code> header, if there is then + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content type value if it exists + */ + public ContentType getContentType() { + return type; + } + + /** + * This is a convenience method that can be used to determine + * the length of the message body. This will determine if there + * is a <code>Content-Length</code> header, if it does then the + * length can be determined, if not then this returns -1. + * + * @return the content length, or -1 if it cannot be determined + */ + public long getContentLength() { + return length; + } + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Transfer-Encoding</code> header, if there is + * then this will parse that header and return the first token in + * the comma separated list of values, which is the primary value. + * + * @return this returns the transfer encoding value if it exists + */ + public String getTransferEncoding() { + return encoding; + } + + /** + * This is a convenience method that can be used to determine the + * content type of the message body. This will determine whether + * there is a <code>Content-Disposition</code> header, if there is + * this will parse that header and represent it as a typed object + * which will expose the various parts of the HTTP header. + * + * @return this returns the content disposition value if it exists + */ + public ContentDisposition getDisposition() { + return disposition; + } + + /** + * This is used to acquire the locales from the request header. The + * locales are provided in the <code>Accept-Language</code> header. + * This provides an indication as to the languages that the client + * accepts. It provides the locales in preference order. + * + * @return this returns the locales preferred by the client + */ + public List<Locale> getLocales() { + if(language != null) { + return language.list(); + } + return Collections.emptyList(); + } + + /** + * This can be used to get the values of HTTP message headers + * that have the specified name. This is a convenience method that + * will present that values as tokens extracted from the header. + * This has obvious performance benefits as it avoids having to + * deal with <code>substring</code> and <code>trim</code> calls. + * <p> + * The tokens returned by this method are ordered according to + * there HTTP quality values, or "q" values, see RFC 2616 section + * 3.9. This also strips out the quality parameter from tokens + * returned. So "image/html; q=0.9" results in "image/html". If + * there are no "q" values present then order is by appearance. + * <p> + * The result from this is either the trimmed header value, that + * is, the header value with no leading or trailing whitespace + * or an array of trimmed tokens ordered with the most preferred + * in the lower indexes, so index 0 is has highest preference. + * + * @param name the name of the headers that are to be retrieved + * + * @return ordered array of tokens extracted from the header(s) + */ + public List<String> getValues(String name) { + return header.getValues(name); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma separated list of + * values the value returned is the first value in that list. + * This returns null if theres no HTTP message header. + * + * @param name the HTTP message header to get the value from + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name) { + return header.getValue(name); + } + + /** + * This can be used to get the value of the first message header + * that has the specified name. The value provided from this will + * be trimmed so there is no need to modify the value, also if + * the header name specified refers to a comma separated list of + * values the value returned is the first value in that list. + * This returns null if there is no HTTP message header. + * + * @param name the HTTP message header to get the value from + * @param index acquires a specific header value from multiple + * + * @return this returns the value that the HTTP message header + */ + public String getValue(String name, int index) { + return header.getValue(name, index); + } + + /** + * This is used to determine if the header represents one that + * requires the HTTP/1.1 continue expectation. If the request + * does require this expectation then it should be send the + * 100 status code which prompts delivery of the message body. + * + * @return this returns true if a continue expectation exists + */ + public boolean isExpectContinue() { + return expect; + } + + /** + * This method is used to add an additional chunk size to the + * internal array. Resizing of the internal array is required as + * the consumed bytes may exceed the initial size of the array. + * In such a scenario the array is expanded the chunk size. + * + * @param size this is the minimum size to expand the array to + */ + @Override + protected void resize(int size) throws IOException { + if(size > limit) { + throw new IOException("Header has exceeded maximum size"); + } + super.resize(size); + } + + /** + * This is used to process the headers when the terminal token + * has been fully read from the consumed bytes. Processing will + * extract all headers from the HTTP header message and further + * parse those values if required. + */ + @Override + protected void process() throws IOException { + headers(); + } + + /** + * This is used to parse the headers from the consumed HTTP header + * and add them to the segment. Once added they are available via + * the header name in a case insensitive manner. If the header has + * a special value, that is, if further information is required it + * will be extracted and exposed in the segment interface. + */ + protected void headers() { + while(pos < count) { + header(); + add(name, value); + } + } + + /** + * This is used to parse a header from the consumed HTTP message + * and add them to the segment. Once added it is available via + * the header name in a case insensitive manner. If the header has + * a special value, that is, if further information is required it + * will be extracted and exposed in the segment interface. + */ + private void header() { + adjust(); + name(); + adjust(); + value(); + end(); + } + + /** + * This is used to add the name and value specified as a special + * header within the segment. Special headers are those where + * there are values of interest to the segment. For instance the + * Content-Length, Content-Type, and Cookie headers are parsed + * using an external parser to extract the values. + * + * @param name this is the name of the header to be added + * @param value this is the value of the header to be added + */ + protected void add(String name, String value) { + if(equal(ACCEPT_LANGUAGE, name)) { + language(value); + }else if(equal(CONTENT_LENGTH, name)) { + length(value); + } else if(equal(CONTENT_TYPE, name)) { + type(value); + } else if(equal(CONTENT_DISPOSITION, name)) { + disposition(value); + } else if(equal(TRANSFER_ENCODING, name)) { + encoding(value); + } else if(equal(EXPECT, name)) { + expect(value); + } else if(equal(COOKIE, name)) { + cookie(value); + } + header.addValue(name, value); + } + + /** + * This is used to determine if the expect continue header is + * present and thus there is a requirement to send the continue + * status before the client sends the request body. This will + * basically assume the expectation is always continue. + * + * @param value the value in the expect continue header + */ + protected void expect(String value) { + expect = true; + } + + /** + * This will accept any cookie header and parse it such that all + * cookies within it are converted to <code>Cookie</code> objects + * and made available as typed objects. If the value can not be + * parsed this will not add the cookie value. + * + * @param value this is the value of the cookie to be parsed + */ + protected void cookie(String value) { + cookies.parse(value); + + for(Cookie cookie : cookies) { + header.setCookie(cookie); + } + } + + /** + * This is used to parse the <code>Accept-Language</code> header + * value. This allows the locales the client is interested in to + * be provided in preference order and allows the client do alter + * and response based on the locale the client has provided. + * + * @param value this is the value that is to be parsed + */ + protected void language(String value) { + language = new LanguageParser(value); + } + + /** + * This is used to parse the content type header header so that + * the MIME type is available to the segment. This provides an + * instance of the <code>ContentType</code> object to represent + * the content type header, which exposes the charset value. + * + * @param value this is the content type value to parse + */ + protected void type(String value) { + type = new ContentTypeParser(value); + } + + /** + * This is used to parse the content disposition header header so + * that the MIME type is available to the segment. This provides + * an instance of the <code>Disposition<code> object to represent + * the content disposition, this exposes the upload type. + * + * @param value this is the content type value to parse + */ + protected void disposition(String value) { + disposition = new ContentDispositionParser(value); + } + + /** + * This is used to store the transfer encoding header value. This + * is used to determine the encoding of the body this segment + * represents. Typically this will be the chunked encoding. + * + * @param value this is the value representing the encoding + */ + protected void encoding(String value) { + encoding = value; + } + + /** + * This is used to parse a provided header value for the content + * length. If the string provided is not an integer value this will + * throw a number format exception, by default length is -1. + * + * @param value this is the header value of the content length + */ + protected void length(String value) { + try { + length = Long.parseLong(value); + }catch(Exception e) { + length = -1; + } + } + + /** + * This updates the token for the header name. The name is parsed + * according to the presence of a colon ':'. Once a colon character + * is encountered then this header name is considered to be read + * from the buffer and is used to key the value after the colon. + */ + private void name() { + Token token = new Token(pos, 0); + + while(pos < count){ + if(array[pos] == ':') { + pos++; + break; + } + token.size++; + pos++; + } + name = token.text(); + } + + + /** + * This is used to parse the HTTP header value. This will parse it + * in such a way that the line can be folded over several lines + * see RFC 2616 for the syntax of a folded line. The folded line + * is basically a way to wrap a single HTTP header into several + * lines using a tab at the start of the following line to indicate + * that the header flows onto the next line. + */ + private void value() { + Token token = new Token(pos, 0); + + scan: for(int mark = 0; pos < count;){ + if(terminal(array[pos])) { /* CR or LF */ + for(int i = 0; pos < count; i++){ + if(array[pos++] == 10) { /* skip the LF */ + if(pos < array.length) { + if(space(array[pos])) { + mark += i + 1; /* account for bytes examined */ + break; /* folding line */ + } + } + break scan; /* not a folding line */ + } + } + } else { + if(!space(array[pos])){ + token.size = ++mark; + } else { + mark++; + } + pos++; + } + } + value = token.text(); + } + + /** + * This will update the offset variable so that the next read will + * be of a non whitespace character. According to RFC 2616 a white + * space character is a tab or a space. This will remove multiple + * occurrences of whitespace characters until an non-whitespace + * character is encountered. + */ + protected void adjust() { + while(pos < count) { + if(!space(array[pos])){ + break; + } + pos++; + } + } + + /** + * This will update the offset variable so that the next read will + * be a non whitespace character or terminal character. According to + * RFC 2616 a white space character is a tab or a space. This will + * remove multiple occurrences of whitespace characters until an + * non-whitespace character or a non-terminal is encountered. This + * is basically used to follow through to the end of a header line. + */ + protected void end() { + while(pos < count) { + if(!white(array[pos])){ + break; + } + pos++; + } + } + + /** + * This method is used to scan for the terminal token. It searches + * for the token and returns the number of bytes in the buffer + * after the terminal token. Returning the excess bytes allows the + * consumer to reset the bytes within the consumer object. + * + * @return this returns the number of excess bytes consumed + */ + @Override + protected int scan() { + int length = count; + + while(pos < count) { + if(array[pos++] != TERMINAL[scan++]) { + scan = 0; + } + if(scan == TERMINAL.length) { + done = true; + count = pos; + pos = 0; + return length - count; + } + } + return 0; + } + + /** + * This is used to determine if two header names are equal, this is + * done to ensure that the case insensitivity of HTTP header names + * is observed. Special headers are processed using this consumer + * and this is used to ensure the correct header is always matched. + * + * @param name this is the name to compare the parsed token with + * @param token this is the header name token to examine + * + * @return true of the header name token is equal to the name + */ + protected boolean equal(String name, String token) { + return name.equalsIgnoreCase(token); + } + + /** + * This identifies a given ISO-8859-1 byte as a space character. A + * space is either a space or a tab character in ISO-8859-1. + * + * @param octet the byte to determine whether it is a space + * + * @return true if it is a space character, false otherwise + */ + protected boolean space(byte octet) { + return octet == ' ' || octet == '\t'; + } + + /** + * This determines if an ISO-8859-1 byte is a terminal character. A + * terminal character is a carriage return or a line feed character. + * + * @param octet the byte to determine whether it is a terminal + * + * @return true if it is a terminal character, false otherwise + */ + protected boolean terminal(byte octet){ + return octet == 13 || octet == 10; + } + + + /** + * This is used to determine if a given ISO-8859-1 byte is a white + * space character, such as a tab or space or a terminal character, + * such as a carriage return or a new line. If it is, this will + * return true otherwise it returns false. + * + * @param octet this is to be checked to see if it is a space + * + * @return true if the byte is a space character, false otherwise + */ + protected boolean white(byte octet) { + switch(octet) { + case ' ': case '\r': + case '\n': case '\t': + return true; + default: + return false; + } + } + + /** + * This is used to provide a string representation of the header + * read. Providing a string representation of the header is used + * so that on debugging the contents of the delivered header can + * be inspected in order to determine a cause of error. + * + * @return this returns a string representation of the header + */ + @Override + public String toString() { + return new String(array, 0, count); + } + + /** + * This is used to track the boundaries of a token so that it can + * be converted in to a usable string. This will track the length + * and offset within the consumed array of the token. When the + * token is to be used it can be converted in to a string. + */ + private class Token { + + /** + * This is used to track the number of bytes within the array. + */ + public int size; + + /** + * This is used to mark the start offset within the array. + */ + public int off; + + /** + * Constructor for the <code>Token</code> object. This is used + * to create a new token to track the range of bytes that will + * be used to create a string representing the parsed value. + * + * @param off the starting offset for the token range + * @param size the number of bytes used for the token + */ + public Token(int off, int size) { + this.off = off; + this.size = size; + } + + /** + * This is used to convert the byte range to a string. This + * will use UTF-8 encoding for the string which is compatible + * with the HTTP default header encoding of ISO-8859-1. + * + * @return the encoded string representing the token + */ + public String text() { + return text("UTF-8"); + } + + /** + * This is used to convert the byte range to a string. This + * will use specified encoding, if that encoding is not + * supported then this will return null for the token value. + * + * @return the encoded string representing the token + */ + public String text(String charset) { + try { + return new String(array, off, size, charset); + } catch(IOException e) { + return null; + } + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/TokenConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/TokenConsumer.java new file mode 100644 index 0000000..2b48b7c --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/TokenConsumer.java @@ -0,0 +1,113 @@ +/* + * TokenConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.common.buffer.Allocator; +import org.simpleframework.common.buffer.Buffer; + +/** + * The <code>TokenConsumer</code> object is used to consume a token + * from the cursor. Once the token has been consumed the consumer + * is finished and the contents of the consumed token is appended + * to an allocated buffer so that it can be extracted. + * + * @author Niall Gallagher + */ +class TokenConsumer extends ArrayConsumer { + + /** + * This is used to allocate a buffer to append the contents. + */ + private Allocator allocator; + + /** + * This is used to append the contents of consumed token. + */ + private Buffer buffer; + + /** + * This is the token that is to be consumed from the cursor. + */ + private byte[] token; + + /** + * This tracks the number of bytes that are read from the token. + */ + private int seek; + + /** + * This is the length of the token that is to be consumed. + */ + private int length; + + /** + * The <code>TokenConsumer</code> object is used to read a token + * from the cursor. This tracks the bytes read from the cursor, + * when it has fully read the token bytes correctly it will + * finish and append the consumed bytes to a buffer. + * + * @param allocator the allocator used to create a buffer + * @param token this is the token that is to be consumed + */ + public TokenConsumer(Allocator allocator, byte[] token) { + this.allocator = allocator; + this.length = token.length; + this.token = token; + this.chunk = length; + } + + /** + * This is used to append the consumed bytes to a created buffer + * so that it can be used when he is finished. This allows the + * contents to be read from an input stream or as a string. + */ + @Override + protected void process() throws IOException { + if(buffer == null) { + buffer = allocator.allocate(length); + } + buffer.append(token); + } + + /** + * This is used to scan the token from the array. Once the bytes + * have been read from the consumed bytes this will return the + * number of bytes that need to be reset within the buffer. + * + * @return this returns the number of bytes to be reset + */ + @Override + protected int scan() throws IOException { + int size = token.length; + int pos = 0; + + if(count >= size) { + while(seek < count) { + if(array[seek++] != token[pos++]) { + throw new IOException("Invalid token"); + } + } + done = true; + return count - seek; + } + return 0; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/message/UpdateConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/message/UpdateConsumer.java new file mode 100644 index 0000000..5d514c9 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/message/UpdateConsumer.java @@ -0,0 +1,143 @@ +/* + * UpdateConsumer.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.message; + +import java.io.IOException; + +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>UpdateConsumer</code> object is used to create a consumer + * that is used to consume and process large bodies. Typically a large + * body will be one that is delivered as part of a multipart upload + * or as a large form POST. The task of the large consumer is to + * consume all the bytes for the body, and reset the cursor after the + * last byte that has been send with the body. This ensures that the + * next character read from the cursor is the first character of a + * HTTP header within the pipeline. + * + * @author Niall Gallagher + */ +public abstract class UpdateConsumer implements BodyConsumer { + + /** + * This is an external array used to copy data between buffers. + */ + protected byte[] array; + + /** + * This is used to determine whether the consumer has finished. + */ + protected boolean finished; + + /** + * Constructor for the <code>UpdateConsumer</code> object. This is + * used to create a consumer with a one kilobyte buffer used to + * read the contents from the cursor and transfer it to the buffer. + */ + protected UpdateConsumer() { + this(2048); + } + + /** + * Constructor for the <code>UpdateConsumer</code> object. This is + * used to create a consumer with a variable size buffer used to + * read the contents from the cursor and transfer it to the buffer. + * + * @param chunk this is the size of the buffer used to read bytes + */ + protected UpdateConsumer(int chunk) { + this.array = new byte[chunk]; + } + + /** + * This is used to determine whether the consumer has finished + * reading. The consumer is considered finished if it has read a + * terminal token or if it has exhausted the stream and can not + * read any more. Once finished the consumed bytes can be parsed. + * + * @return true if the consumer has finished reading its content + */ + public boolean isFinished() { + return finished; + } + + /** + * This method is used to consume bytes from the provided cursor. + * Consuming of bytes from the cursor should be done in such a + * way that it does not block. So typically only the number of + * ready bytes in the <code>ByteCursor</code> object should be + * read. If there are no ready bytes then this will return. + * + * @param cursor used to consume the bytes from the HTTP pipeline + */ + public void consume(ByteCursor cursor) throws IOException { + int ready = cursor.ready(); + + while(ready > 0) { + int size = Math.min(ready, array.length); + int count = cursor.read(array, 0, size); + + if(count > 0) { + int reset = update(array, 0, count); + + if(reset > 0) { + cursor.reset(reset); + } + } + if(finished) { + commit(cursor); + break; + } + ready = cursor.ready(); + } + } + + /** + * This method can be used to commit the consumer when all data + * has been consumed. It is often used to push back some data on + * to the cursor so that the next consumer can read valid tokens + * from the stream of bytes. If no commit is required then the + * default implementation of this will simply return quietly. + * + * @param cursor this is the cursor used by this consumer + */ + protected void commit(ByteCursor cursor) throws IOException { + if(!finished) { + throw new IOException("Consumer not finished"); + } + } + + /** + * This is used to process the bytes that have been read from the + * cursor. Depending on the delimiter used this knows when the + * end of the body has been encountered. If the end is encountered + * this method must return the number of bytes overflow, and set + * the state of the consumer to finished. + * + * @param array this is a chunk read from the cursor + * @param off this is the offset within the array the chunk starts + * @param count this is the number of bytes within the array + * + * @return this returns the number of bytes overflow that is read + */ + protected abstract int update(byte[] array, int off, int count) throws IOException; +} + + diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/AddressParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/AddressParser.java new file mode 100644 index 0000000..06d5509 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/AddressParser.java @@ -0,0 +1,1347 @@ +/* + * AddressParser.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import org.simpleframework.common.KeyMap; +import org.simpleframework.common.parse.Parser; +import org.simpleframework.http.Address; +import org.simpleframework.http.Path; +import org.simpleframework.http.Query; + +/** + * This parser is used to parse uniform resource identifiers. + * The uniform resource identifier syntax is given in RFC 2396. + * This parser can parse relative and absolute URI's. The + * uniform resource identifier syntax that this parser will + * parse are based on the generic web based URL similar to + * the syntax represented in RFC 2616 section 3.2.2. The syntax + * used to parse this URI is a modified version of RFC 2396 + * <pre> + * + * URI = (absoluteURI | relativeURI) + * absoluteURI = scheme ":" ("//" netpath | relativeURI) + * relativeURI = path ["?" querypart] + * netpath = domain [":" port] relativeURI + * path = *("/" segment) + * segment = *pchar *( ";" param ) + * + * </pre> + * This implements the <code>Address</code> interface and provides + * methods that access the various parts of the URI. The parameters + * in the path segments of the uniform resource identifier are + * stored in name value pairs. If parameter names are not unique + * across the path segments then only the deepest parameter will be + * stored from the path segment. For example if the URI represented + * was <code>http://domain/path1;x=y/path2;x=z</code> the value for + * the parameter named <code>x</code> would be <code>z</code>. + * <p> + * This will normalize the path part of the uniform resource + * identifier. A normalized path is one that contains no back + * references like "./" and "../". The normalized path will not + * contain the path parameters. + * <p> + * The <code>setPath</code> method is used to reset the path this + * uniform resource identifier has, it also resets the parameters. + * The parameters are extracted from the new path given. + * + * @author Niall Gallagher + */ +public class AddressParser extends Parser implements Address { + + /** + * Parameters are stored so that the can be viewed. + */ + private ParameterMap param; + + /** + * This is the path used to represent the address path. + */ + private Path normal; + + /** + * This contains the query parameters for the address. + */ + private Query data; + + /** + * Used to track the characters that form the path. + */ + private Token path; + + /** + * Used to track the characters that form the domain. + */ + private Token domain; + + /** + * Used to track the characters that form the query. + */ + private Token query; + + /** + * Used to track the name characters of a parameter. + */ + private Token name; + + /** + * Used to track the value characters of a parameter. + */ + private Token value; + + /** + * References the scheme that this URI contains. + */ + private Token scheme; + + /** + * Contains the port number if it was specified. + */ + private int port; + + /** + * Default constructor will create a <code>AddressParser</code> + * that contains no specifics. The instance will return + * <code>null</code> for all the get methods. The parsers + * get methods are populated by using the <code>parse</code> + * method. + */ + public AddressParser(){ + this.param = new ParameterMap(); + this.path = new Token(); + this.domain = new Token(); + this.query = new Token(); + this.scheme = new Token(); + this.name = new Token(); + this.value = new Token(); + } + + /** + * This is primarily a convenience constructor. This will parse + * the <code>String</code> given to extract the specifics. This + * could be achieved by calling the default no-arg constructor + * and then using the instance to invoke the <code>parse</code> + * method on that <code>String</code> to extract the parts. + * + * @param text a <code>String</code> containing a URI value + */ + public AddressParser(String text){ + this(); + parse(text); + } + + /** + * This allows the scheme of the URL given to be returned. + * If the URI does not contain a scheme then this will + * return null. The scheme of the URI is the part that + * specifies the type of protocol that the URI is used + * for, an example <code>gopher://domain/path</code> is + * a URI that is intended for the gopher protocol. The + * scheme is the string <code>gopher</code>. + * + * @return this returns the scheme tag for the URI if + * there is one specified for it + */ + public String getScheme(){ + return scheme.toString(); + } + + /** + * This is used to retrieve the domain of this URI. The + * domain part in the URI is an optional part, an example + * <code>http://domain/path?querypart</code>. This will + * return the value of the domain part. If there is no + * domain part then this will return null otherwise the + * domain value found in the uniform resource identifier. + * + * @return the domain part of this uniform resource + * identifier this represents + */ + public String getDomain(){ + return domain.toString(); + } + + /** + * This is used to retrieve the path of this URI. The path part + * is the most fundamental part of the URI. This will return + * the value of the path. If there is no path part then this + * will return <code>/</code> to indicate the root. + * <p> + * The <code>Path</code> object returned by this will contain + * no path parameters. The path parameters are available using + * the <code>Address</code> methods. The reason that this does not + * contain any of the path parameters is so that if the path is + * needed to be converted into an OS specific path then the path + * parameters will not need to be separately parsed out. + * + * @return the path that this URI contains, this value will not + * contain any back references such as "./" and "../" or any + * path parameters + */ + public Path getPath(){ + if(normal == null) { + String text = path.toString(); + + if(text == null) { + normal = new PathParser("/"); + } + if(normal == null){ + normal = new PathParser(text); + } + } + return normal; + } + + /** + * This is used to retrieve the query of this URI. The query part + * in the URI is an optional part. This will return the value + * of the query part. If there is no query part then this will + * return an empty <code>Query</code> object. The query is + * an optional member of a URI and comes after the path part, it + * is preceded by a question mark, <code>?</code> character. + * For example the following URI contains <code>query</code> for + * its query part, <code>http://host:port/path?query</code>. + * <p> + * This returns a <code>org.simpleframework.http.Query</code> + * object that can be used to interact directly with the query + * values. The <code>Query</code> object is a read-only interface + * to the query parameters, and so will not affect the URI. + * + * @return a <code>Query</code> object for the query part + */ + public Query getQuery(){ + if(data == null) { + String text = query.toString(); + + if(text == null) { + data = new QueryParser(); + } + if(data == null){ + data = new QueryParser(text); + } + } + return data; + } + + /** + * This is used to retrieve the port of the uniform resource + * identifier. The port part in this is an optional part, an + * example <code>http://host:port/path?querypart</code>. This + * will return the value of the port. If there is no port then + * this will return <code>-1</code> because this represents + * an impossible uniform resource identifier port. The port + * is an optional part. + * + * @return this returns the port of the uniform resource + * identifier + */ + public int getPort(){ + return port <= 0? -1 : port; + } + + /** + * This extracts the parameter values from the uniform resource + * identifier represented by this object. The parameters that a + * uniform resource identifier contains are embedded in the path + * part of the URI. If the path contains no parameters then this + * will return an empty <code>Map</code> instance. + * <p> + * This will produce unique name and value parameters. Thus if the + * URI contains several path segments with similar parameter names + * this will return the deepest parameter. For example if the URI + * represented was <code>http://domain/path1;x=y/path2;x=z</code> + * the value for the parameter named <code>x</code> would be + * <code>z</code>. + * + * @return this will return the parameter names found in the URI + */ + public KeyMap<String> getParameters(){ + return param; + } + + /** + * This allows the scheme for the URI to be specified. + * If the URI does not contain a scheme then this will + * attach the scheme and the <code>://</code> identifier + * to ensure that the <code>Address.toString</code> will + * produce the correct syntax. + * <p> + * Caution must be taken to ensure that the port and + * the scheme are consistent. So if the original URI + * was <code>http://domain:80/path</code> and the scheme + * was changed to <code>ftp</code> the port number that + * remains is the standard HTTP port not the FTP port. + * + * @param value this specifies the protocol this URI + * is intended for + */ + public void setScheme(String value){ + scheme.value = value; + } + + /** + * This will set the domain to whatever value is in the + * string parameter. If the string is null then this URI + * objects <code>toString</code> method will not contain + * the domain. The result of the <code>toString</code> + * method will be <code>/path/path?query</code>. If the + * path is non-null this URI will contain the path. + * + * @param value this will be the new domain of this + * uniform resource identifier, if it is not null + */ + public void setDomain(String value){ + path.toString(); + query.toString(); + scheme.toString(); + domain.clear(); + parseDomain(value); + } + + /** + * This will set the domain to whatever value is in the + * string parameter. If the string is null then this URI + * objects <code>toString</code> method will not contain + * the domain. The result of the <code>toString</code> + * method will be <code>/path/path?query</code>. If the + * path is non-null this URI will contain the path. + * + * @param value this will be the new domain of this + * uniform resource identifier, if it is not null + */ + private void parseDomain(String value){ + count = value.length(); + ensureCapacity(count); + value.getChars(0, count, buf, 0); + normal = null; + off = 0; + hostPort(); + } + + /** + * This will set the port to whatever value it is given. If + * the value is 0 or less then the <code>toString</code> will + * will not contain the optional port. If port number is above + * 0 then the <code>toString</code> method will produce a URI + * like <code>http://host:123/path</code> but only if there is + * a valid domain. + * + * @param port the port value that this URI is to have + */ + public void setPort(int port) { + this.port = port; + } + + /** + * This will set the path to whatever value it is given. If the + * value is null then this <code>Address.toString</code> method will + * not contain the path, that is if path is null then it will be + * interpreted as <code>/</code>. + * <p> + * This will reset the parameters this URI has. If the value + * given to this method has embedded parameters these will form + * the parameters of this URI. The value given may not be the + * same value that the <code>getPath</code> produces. The path + * will have all back references and parameters stripped. + * + * @param text the path that this URI is to be set with + */ + public void setPath(String text) { + if(!text.startsWith("/")){ + text = "/" + text; + } + domain.toString(); + query.toString(); + scheme.toString(); + param.clear(); + path.clear(); + parsePath(text); /*extract params*/ + } + + /** + * This will set the path to whatever value it is given. If the + * value is null then this <code>Address.toString</code> method + * will not contain the path, that is if path is null then it will + * be interpreted as <code>/</code>. + * <p> + * This will reset the parameters this URI has. If the value + * given to this method has embedded parameters these will form + * the parameters of this URI. The value given may not be the + * same value that the <code>getPath</code> produces. The path + * will have all back references and parameters stripped. + * + * @param path the path that this URI is to be set with + */ + public void setPath(Path path) { + if(path != null){ + normal = path; + }else { + setPath("/"); + } + } + + /** + * This is used to parse the path given with the <code>setPath</code> + * method. The path contains name and value pairs. These parameters + * are embedded into the path segments using a semicolon character, + * ';'. Since the parameters to not form part of the actual path + * mapping they are removed from the path and stored. Each parameter + * can then be extracted from this parser using the methods provided + * by the <code>Address</code> interface. + * + * @param path this is the path that is to be parsed and have the + * parameter values extracted + */ + private void parsePath(String path){ + count = path.length(); + ensureCapacity(count); + path.getChars(0, count, buf, 0); + normal = null; + off = 0; + path(); + } + + /** + * This will set the query to whatever value it is given. If the + * value is null then this <code>Address.toString</code> method + * will not contain the query. If the query was <code>abc</code> + * then the <code>toString</code> method would produce a string + * like <code>http://host:port/path?abc</code>. If the query is + * null this URI would have no query part. The query must not + * contain the <code>?</code> character. + * + * @param value the query that this uniform resource identifier + * is to be set to if it is non-null + */ + public void setQuery(String value) { + query.value = value; + data = null; + } + + /** + * This will set the query to whatever value it is given. If the + * value is null then this <code>Address.toString</code> method + * will not contain the query. If the <code>Query.toString</code> + * returns null then the query will be empty. This is basically + * the <code>setQuery(String)</code> method with the string value + * from the issued <code>Query.toString</code> method. + * + * @param query a <code>Query</code> object that contains + * the name value parameters for the query + */ + public void setQuery(Query query) { + if(value != null) { + data = query; + }else { + setQuery(""); + } + } + + /** + * This will check to see what type of URI this is if it is an + * <code>absoluteURI</code> or a <code>relativeURI</code>. To + * see the definition of a URI see RFC 2616 for the definition + * of a URL and for more specifics see RFC 2396 for the + * expressions. + */ + protected void parse(){ + if(count > 0){ + if(buf[0] == '/'){ + relativeURI(); + }else{ + absoluteURI(); + } + } + } + + /** + * This will empty each tokens cache. A tokens cache is used + * to represent a token once the token's <code>toString</code> + * method has been called. Thus when the <code>toString</code> + * method is called then the token depends on the value of the + * cache alone in further calls to <code>toString</code>. + * However if a URI has just been parsed and that method has + * not been invoked then the cache is created from the buf if + * its length is greater than zero. + */ + protected void init(){ + param.clear(); + domain.clear(); + path.clear(); + query.clear(); + scheme.clear(); + off =port = 0; + normal = null; + data = null; + } + + /** + * This is a specific definition of a type of URI. An absolute + * URI is a URI that contains a host and port. It is the most + * frequently used type of URI. This will define the host and + * the optional port part. As well as the relative URI part. + * This uses a simpler syntax than the one specified in RFC 2396 + * <code><pre> + * + * absoluteURI = scheme ":" ("//" netpath | relativeURI) + * relativeURI = path ["?" querypart] + * netpath = domain [":" port] relativeURI + * path = *("/" segment) + * segment = *pchar *( ";" param ) + * + * </pre></code> + * This syntax is sufficient to handle HTTP style URI's as well + * as GOPHER and FTP and various other 'simple' schemes. See + * RFC 2396 for the syntax of an <code>absoluteURI</code>. + */ + private void absoluteURI(){ + scheme(); + netPath(); + } + + /** + * This will check to see if there is a scheme in the URI. If + * there is a scheme found in the URI this returns true and + * removes that scheme tag of the form "ftp:" or "http:" + * or whatever the protocol scheme tag may be for the URI. + * <p> + * The syntax for the scheme is given in RFC 2396 as follows + * <code><pre> + * + * scheme = alpha *( alpha | digit | "+" | "-" | "." ) + * + * </pre></code> + * This will however also skips the "://" from the tag + * so of the URI was <code>gopher://domain/path</code> then + * the URI would be <code>domain/path</code> afterwards. + */ + private void scheme(){ + int mark = off; + int pos = off; + + if(alpha(buf[off])){ + while(off < count){ + char next = buf[off++]; + + if(schemeChar(next)){ + pos++; + }else if(next == ':'){ + if(!skip("//")) { + off = mark; + pos = mark; + } + break; + }else{ + off = mark; + pos = mark; + break; + } + } + scheme.len = pos - mark; + scheme.off = mark; + } + } + + /** + * This method is used to assist the scheme method. This will + * check to see if the type of the character is the same as + * those described in RFC 2396 for a scheme character. The + * scheme tag can contain an alphanumeric of the following + * <code>"+", "-", "."</code>. + * + * @param c this is the character that is being checked + * + * @return this returns true if the character is a valid + * scheme character + */ + private boolean schemeChar(char c){ + switch(c){ + case '+': case '-': + case '.': + return true; + default: + return alphanum(c); + } + } + + /** + * The network path is the path that contains the network + * address of the host that this URI is targeted at. This + * will parse the domain name of the host and also a port + * number before parsing a relativeURI + * <code><pre> + * + * netpath = domain [":" port] relativeURI + * + * </pre></code> + * This syntax is modified from the URI specification on + * RFC 2396. + */ + private void netPath(){ + hostPort(); + relativeURI(); + } + + /** + * This is used to extract the host and port combination. + * Typically a URI will not explicitly specify a port, however + * if there is a semicolon at the end of the domain it should + * be interpreted as the port part of the URI. + */ + private void hostPort() { + domain(); + if(skip(":")){ + port(); + } + } + + /** + * This is a specific definition of a type of URI. A relative + * URI is a URI that contains no host or port. It is basically + * the resource within the host. This will extract the path and + * the optional query part of the URI. Rfc2396 has the proper + * definition of a <code>relativeURI</code>. + */ + private void relativeURI(){ + path(); + if(skip("?")){ + query(); + } + } + + /** + * This is used to extract the optional port from a given URI. + * This will read a sequence of digit characters and convert + * the <code>String</code> of digit characters into a decimal + * number. The digits will be added to the port variable. If + * there is no port number this will not update the read offset. + */ + private void port() { + while(off < count){ + if(!digit(buf[off])){ + break; + } + port *= 10; + port += buf[off]; + port -= '0'; + off++; + } + } + + /** + * This is used to extract the domain from the given URI. This + * will firstly initialize the token object that represents the + * domain. This allows the token's <code>toString</code> method to + * return the extracted value of the token rather than getting + * confused with previous values set by a previous parse method. + * <p> + * This uses the following delimiters to determine the end of the + * domain <code>?</code>,<code>:</code> and <code>/<code>. This + * ensures that the read offset does not go out of bounds and + * consequently throw an <code>IndexOutOfBoundsException</code>. + */ + private void domain(){ + int mark = off; + + loop: while(off < count){ + switch(buf[off]){ + case '/': case ':': + case '?': + break loop; + default: + off++; + } + } + domain.len = off - mark; + domain.off = mark; + } + + /** + * This is used to extract the segments from the given URI. This + * will firstly initialize the token object that represents the + * path. This allows the token's <code>toString</code> method to + * return the extracted value of the token rather than getting + * confused with previous values set by a previous parse method. + * <p> + * This is slightly different from RFC 2396 in that it defines a + * pchar as the RFC 2396 definition of a pchar without the escaped + * chars. So this method has to ensure that no escaped chars go + * unchecked. This ensures that the read offset does not go out + * of bounds and throw an <code>IndexOutOfBoundsException</code>. + */ + private void path(){ + int mark = off; + int pos = off; + + while(skip("/")) { + buf[pos++] = '/'; + + while(off < count){ + if(buf[off]==';'){ + while(skip(";")){ + param(); + insert(); + } + break; + } + if(buf[off]=='%'){ + escape(); + }else if(!pchar(buf[off])){ + break; + } + buf[pos++]=buf[off++]; + } + } + path.len = pos -mark; + path.off = mark; + } + + /** + * This is used to extract the query from the given URI. This + * will firstly initialize the token object that represents the + * query. This allows the token's <code>toString</code> method + * to return the extracted value of the token rather than getting + * confused with previous values set by a previous parse method. + * The calculation of the query part of a URI is basically the + * end of the URI. + */ + private void query() { + query.len = count - off; + query.off = off; + } + + /** + * This is an expression that is defined by RFC 2396 it is used + * in the definition of a segment expression. This is basically + * a list of pchars. + * <p> + * This method has to ensure that no escaped chars go unchecked. + * This ensures that the read offset does not goe out of bounds + * and consequently throw an out of bounds exception. + */ + private void param() { + name(); + if(skip("=")){ /* in case of error*/ + value(); + } + } + + /** + * This extracts the name of the parameter from the character + * buffer. The name of a parameter is defined as a set of + * pchars including escape sequences. This will extract the + * parameter name and buffer the chars. The name ends when a + * equals character, "=", is encountered or in the case of a + * malformed parameter when the next character is not a pchar. + */ + private void name(){ + int mark = off; + int pos = off; + + while(off < count){ + if(buf[off]=='%'){ /* escaped */ + escape(); + }else if(buf[off]=='=') { + break; + }else if(!pchar(buf[off])){ + break; + } + buf[pos++] = buf[off++]; + } + name.len = pos - mark; + name.off = mark; + } + + /** + * This extracts a parameter value from a path segment. The + * parameter value consists of a sequence of pchars and some + * escape sequences. The parameter value is buffered so that + * the name and values can be paired. The end of the value + * is determined as the end of the buffer or the last pchar. + */ + private void value(){ + int mark = off; + int pos = off; + + while(off < count){ + if(buf[off]=='%'){ /* escaped */ + escape(); + }else if(!pchar(buf[off])) { + break; + } + buf[pos++] = buf[off++]; + } + value.len = pos - mark; + value.off = mark; + } + + /** + * This method adds the name and value to a map so that the next + * name and value can be collected. The name and value are added + * to the map as string objects. Once added to the map the + * <code>Token</code> objects are set to have zero length so they + * can be reused to collect further values. This will add the + * values to the map as an array of type string. This is done so + * that if there are multiple values that they can be stored. + */ + private void insert(){ + if(value.length() > 0){ + if(name.length() > 0) + insert(name,value); + } + name.clear(); + value.clear(); + } + + /** + * This will add the given name and value to the parameters map. + * This will only store a single value per parameter name, so + * only the parameter that was latest encountered will be saved. + * The <code>getQuery</code> method can be used to collect + * the parameter values using the parameter name. + * + * @param name this is the name of the value to be inserted + * @param value this is the value of a that is to be inserted + */ + private void insert(Token name, Token value){ + insert(name.toString(), value.toString()); + } + + /** + * This will add the given name and value to the parameters map. + * This will only store a single value per parameter name, so + * only the parameter that was latest encountered will be saved. + * The <code>getQuery</code> method can be used to collect + * the parameter values using the parameter name. + * + * @param name this is the name of the value to be inserted + * @param value this is the value of a that is to be inserted + */ + private void insert(String name, String value) { + param.put(name, value); + } + + /** + * This converts an encountered escaped sequence, that is all + * embedded hexidecimal characters into a native UCS character + * value. This does not take any characters from the stream it + * just prepares the buffer with the correct byte. The escaped + * sequence within the URI will be interpreded as UTF-8. + * <p> + * This will leave the next character to read from the buffer + * as the character encoded from the URI. If there is a fully + * valid escaped sequence, that is <code>"%" HEX HEX</code>. + * This decodes the escaped sequence using UTF-8 encoding, all + * encoded sequences should be in UCS-2 to fit in a Java char. + */ + private void escape() { + int peek = peek(off); + + if(!unicode(peek)) { + binary(peek); + } + } + + /** + * This method determines, using a peek character, whether the + * sequence of escaped characters within the URI is binary data. + * If the data within the escaped sequence is binary then this + * will ensure that the next character read from the URI is the + * binary octet. This is used strictly for backward compatible + * parsing of URI strings, binary data should never appear. + * + * @param peek this is the first escaped character from the URI + * + * @return currently this implementation always returns true + */ + private boolean binary(int peek) { + if(off + 2 < count) { + off += 2; + buf[off]= bits(peek); + } + return true; + } + + /** + * This method determines, using a peek character, whether the + * sequence of escaped characters within the URI is in UTF-8. If + * a UTF-8 character can be successfully decoded from the URI it + * will be the next character read from the buffer. This can + * check for both UCS-2 and UCS-4 characters. However, because + * the Java <code>char</code> can only hold UCS-2, the UCS-4 + * characters will have only the low order octets stored. + * <p> + * The WWW Consortium provides a reference implementation of a + * UTF-8 decoding for Java, in this the low order octets in the + * UCS-4 sequence are used for the character. So, in the + * absence of a defined behaviour, the W3C behaviour is assumed. + * + * @param peek this is the first escaped character from the URI + * + * @return this returns true if a UTF-8 character is decoded + */ + private boolean unicode(int peek) { + if((peek & 0x80) == 0x00){ + return unicode(peek, 0); + } + if((peek & 0xe0) == 0xc0){ + return unicode(peek & 0x1f, 1); + } + if((peek & 0xf0) == 0xe0){ + return unicode(peek & 0x0f, 2); + } + if((peek & 0xf8) == 0xf0){ + return unicode(peek & 0x07, 3); + } + if((peek & 0xfc) == 0xf8){ + return unicode(peek & 0x03, 4); + } + if((peek & 0xfe) == 0xfc){ + return unicode(peek & 0x01, 5); + } + return false; + } + + /** + * This method will decode the specified amount of escaped + * characters from the URI and convert them into a single Java + * UCS-2 character. If there are not enough characters within + * the URI then this will return false and leave the URI alone. + * <p> + * The number of characters left is determined from the first + * UTF-8 octet, as specified in RFC 2279, and because this is + * a URI there must that number of <code>"%" HEX HEX</code> + * sequences left. If successful the next character read is + * the UTF-8 sequence decoded into a native UCS-2 character. + * + * @param peek contains the bits read from the first UTF octet + * @param more this specifies the number of UTF octets left + * + * @return this returns true if a UTF-8 character is decoded + */ + private boolean unicode(int peek, int more) { + if(off + more * 3 >= count) { + return false; + } + return unicode(peek,more,off); + } + + /** + * This will decode the specified amount of trailing UTF-8 bits + * from the URI. The trailing bits are those following the first + * UTF-8 octet, which specifies the length, in octets, of the + * sequence. The trailing octets are if the form 10xxxxxx, for + * each of these octets only the last six bits are valid UCS + * bits. So a conversion is basically an accumulation of these. + * <p> + * If at any point during the accumulation of the UTF-8 bits + * there is a parsing error, then parsing is aborted an false + * is returned, as a result the URI is left unchanged. + * + * @param peek bytes that have been accumulated from the URI + * @param more this specifies the number of UTF octets left + * @param pos this specifies the position the parsing begins + * + * @return this returns true if a UTF-8 character is decoded + */ + private boolean unicode(int peek, int more, int pos) { + while(more-- > 0) { + if(buf[pos] == '%'){ + int next = pos + 3; + int hex = peek(next); + + if((hex & 0xc0) == 0x80){ + peek = (peek<<6)|(hex&0x3f); + pos = next; + continue; + } + } + return false; + } + if(pos + 2 < count) { + off = pos + 2; + buf[off]= bits(peek); + } + return true; + } + + /** + * Defines behaviour for UCS-2 versus UCS-4 conversion from four + * octets. The UTF-8 encoding scheme enables UCS-4 characters to + * be encoded and decodeded. However, Java supports the 16-bit + * UCS-2 character set, and so the 32-bit UCS-4 character set is + * not compatable. This basically decides what to do with UCS-4. + * + * @param data up to four octets to be converted to UCS-2 format + * + * @return this returns a native UCS-2 character from the int + */ + private char bits(int data) { + return (char)data; + } + + /** + * This will return the escape expression specified from the URI + * as an integer value of the hexidecimal sequence. This does + * not make any changes to the buffer it simply checks to see if + * the characters at the position specified are an escaped set + * characters of the form <code>"%" HEX HEX</code>, if so, then + * it will convert that hexidecimal string in to an integer + * value, or -1 if the expression is not hexidecimal. + * + * @param pos this is the position the expression starts from + * + * @return the integer value of the hexidecimal expression + */ + private int peek(int pos) { + if(buf[pos] == '%'){ + if(count <= pos + 2) { + return -1; + } + char high = buf[pos + 1]; + char low = buf[pos + 2]; + + return convert(high, low); + } + return -1; + } + + /** + * This will convert the two hexidecimal characters to a real + * integer value, which is returned. This requires characters + * within the range of 'A' to 'F' and 'a' to 'f', and also + * the digits '0' to '9'. The characters encoded using the + * ISO-8859-1 encoding scheme, if the characters are not with + * in the range specified then this returns -1. + * + * @param high this is the high four bits within the integer + * @param low this is the low four bits within the integer + * + * @return this returns the indeger value of the conversion + */ + private int convert(char high, char low) { + int hex = 0x00; + + if(hex(high) && hex(low)){ + if('A' <= high && high <= 'F'){ + high -= 'A' - 'a'; + } + if(high >= 'a') { + hex ^= (high-'a')+10; + } else { + hex ^= high -'0'; + } + hex <<= 4; + + if('A' <= low && low <= 'F') { + low -= 'A' - 'a'; + } + if(low >= 'a') { + hex ^= (low-'a')+10; + } else { + hex ^= low-'0'; + } + return hex; + } + return -1; + } + + /** + * This is used to determine wheather a char is a hexidecimal + * <code>char</code> or not. A hexidecimal character is consdered + * to be a character within the range of <code>0 - 9</code> and + * between <code>a - f</code> and <code>A - F</code>. This will + * return <code>true</code> if the character is in this range. + * + * @param ch this is the character which is to be determined here + * + * @return true if the character given has a hexidecimal value + */ + private boolean hex(char ch) { + if(ch >= '0' && ch <= '9') { + return true; + } else if(ch >='a' && ch <= 'f') { + return true; + } else if(ch >= 'A' && ch <= 'F') { + return true; + } + return false; + } + + /** + * This is a character set defined by RFC 2396 it is used to + * determine the valididity of certain <code>chars</code> + * within a Uniform Resource Identifier. RFC 2396 defines + * an unreserved char as <code>alphanum | mark</code>. + * + * @param c the character value that is being checked + * + * @return true if the character has an unreserved value + */ + private boolean unreserved(char c){ + return mark(c) || alphanum(c); + } + + /** + * This is used to determine wheather or not a given unicode + * character is an alphabetic character or a digit character. + * That is withing the range <code>0 - 9</code> and between + * <code>a - z</code> it uses <code>iso-8859-1</code> to + * compare the character. + * + * @param c the character value that is being checked + * + * @return true if the character has an alphanumeric value + */ + private boolean alphanum(char c){ + return digit(c) || alpha(c); + } + + /** + * This is used to determine wheather or not a given unicode + * character is an alphabetic character. This uses encoding + * <code>iso-8859-1</code> to compare the characters. + * + * @param c the character value that is being checked + * + * @return true if the character has an alphabetic value + */ + private boolean alpha(char c){ + return (c <= 'z' && 'a' <= c) || + (c <= 'Z' && 'A' <= c); + } + + /** + * This is a character set defined by RFC 2396 it checks + * the valididity of cetain chars within a uniform resource + * identifier. The RFC 2396 defines a mark char as <code>"-", + * "_", ".", "!", "~", "*", "'", "(", ")"</code>. + * + * @param c the character value that is being checked + * + * @return true if the character is a mark character + */ + private boolean mark(char c){ + switch(c){ + case '-': case '_': case '.': + case '!': case '~': case '*': + case '\'': case '(': case ')': + return true; + default: + return false; + } + } + + /** + * This is a character set defined by RFC 2396 it is used to check + * the valididity of cetain chars within a generic uniform resource + * identifier. The RFC 2396 defines a pchar char as unreserved or + * escaped or one of the following characters <code>":", "@", "=", + * "&", "+", "$", ","</code> this will not check to see if the + * char is an escaped char, that is <code>% HEX HEX</code>. Because + * this takes 3 chars. + * + * @param c the character value that is being checked + * + * @return true if the character is a pchar character + */ + private boolean pchar(char c){ + switch(c){ + case '@': case '&': case '=': + case '+': case '$': case ',': + case ':': + return true; + default: + return unreserved(c); + } + } + + /** + * This is a character set defined by RFC 2396, it checks the + * valididity of certain chars in a uniform resource identifier. + * The RFC 2396 defines a reserved char as <code>";", "/", "?", + * ":", "@", "&", "=", "+", "$", ","</code>. + * + * @param c the character value that is being checked + * + * @return true if the character is a reserved character + */ + private boolean reserved(char c){ + switch(c){ + case ';': case '/': case '?': + case '@': case '&': case ':': + case '=': case '+': case '$': + case ',': + return true; + default: + return false; + } + } + + /** + * This is used to convert this URI object into a <code>String</code> + * object. This will only convert the parts of the URI that exist, so + * the URI may not contain the domain or the query part and it will + * not contain the path parameters. If the URI contains all these + * parts then it will return somthing like + * <pre> + * scheme://host:port/path/path?querypart + * </pre> + * <p> + * It can return <code>/path/path?querypart</code> style relative + * URI's. If any of the parts are set to null then that part will be + * missing, for example if <code>setDomain</code> method is invoked + * with a null parameter then the domain and port will be missing + * from the resulting URI. If the path part is set to null using the + * <code>setPath</code> then the path will be <code>/</code>. An + * example URI with the path part of null would be + * <pre> + * scheme://host:port/?querypart + * </pre> + * + * @return the URI with only the path part and the non-null optional + * parts of the uniform resource identifier + */ + public String toString() { + return (scheme.length() > 0 ? scheme +"://": "") + + (domain.length() > 0 ? domain + + (port > 0 ? ":"+port : "") : "")+ getPath() + + (param.size() > 0 ? param : "")+ + (query.length()>0?"?"+query :""); + } + + /** + * The <code>ParameterMap</code> is uses to store the parameters + * that are to be encoded in to the address. This will append all + * of the parameters to the end of the path. These can later be + * extracted by parsing the address. + * + * @author Niall Gallagher + */ + private class ParameterMap extends KeyMap<String> { + + /** + * This will return the parameters encoded in such a way that + * it can be appended to the end of the path. These parameters + * can be added to the address such that they do not form a + * query parameter. Values such as session identifiers are + * often added as the path parameters to the address. + * + * @return this returns the representation of the parameters + */ + private String encode() { + StringBuilder text = new StringBuilder(); + + for(String name : param) { + String value = param.get(name); + + text.append(";"); + text.append(name); + + if(value != null) { + text.append("="); + text.append(value);; + } + } + return text.toString(); + } + + /** + * This will return the parameters encoded in such a way that + * it can be appended to the end of the path. These parameters + * can be added to the address such that they do not form a + * query parameter. Values such as session identifiers are + * often added as the path parameters to the address. + * + * @return this returns the representation of the parameters + */ + public String toString() { + return encode(); + } + } + + /** + * This is used as an alternative to the <code>ParseBuffer</code> + * for extracting tokens from the URI without allocating memory. + * This will basically mark out regions within the buffer which are + * used to represent the token. When the token value is required + * the region is used to create a <code>String</code> object. + */ + private class Token { + + /** + * This can be used to override the value for this token. + */ + public String value; + + /** + * This represents the start offset within the buffer. + */ + public int off; + + /** + * This represents the number of charters in the token. + */ + public int len; + + /** + * If the <code>Token</code> is to be reused this will clear + * all previous data. Clearing the buffer allows it to be + * reused if there is a new URI to be parsed. This ensures + * that a null is returned if the token length is zero. + */ + public void clear() { + value = null; + len = 0; + } + + /** + * This is used to determine the number of characters this + * token contains. This is used rather than accessing the + * length directly so that the value the token represents + * can be overridden easily without upsetting the token. + * + * @return this returns the number of characters this uses + */ + public int length() { + if(value == null){ + return len; + } + return value.length(); + } + + /** + * This method will convert the <code>Token</code> into it's + * <code>String</code> equivelant. This will firstly check + * to see if there is a value, for the string representation, + * if there is the value is returned, otherwise the region + * is converted into a <code>String</code> and returned. + * + * @return this returns a value representing the token + */ + public String toString() { + if(value != null) { + return value; + } + if(len > 0) { + value = new String(buf,off,len); + } + return value; + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/ContentDispositionParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/ContentDispositionParser.java new file mode 100644 index 0000000..b7fe6c2 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/ContentDispositionParser.java @@ -0,0 +1,296 @@ +/* + * ContentDispositionParser.java February 2007 + * + * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import org.simpleframework.common.parse.ParseBuffer; +import org.simpleframework.common.parse.Parser; +import org.simpleframework.http.ContentDisposition; + +/** + * The <code>ContentDispositionParser</code> object is used to represent + * a parser used to parse the Content-Disposition header. Its used when + * there is a multipart form upload to the server and allows the + * server to determine the individual part types. + * + * @author Niall Gallagher + */ +public class ContentDispositionParser extends Parser implements ContentDisposition { + + /** + * This is the buffer used to acquire values from the header. + */ + private ParseBuffer skip; + + /** + * This is used to capture the name of the file if it is provided. + */ + private ParseBuffer file; + + /** + * This is used to capture the name of the part if it is provided. + */ + private ParseBuffer name; + + /** + * This is used to determine if the disposition is a file or form. + */ + private boolean form; + + /** + * Constructor for the <code>ContentDispositionParser</code> object. + * This is used to create a parser that can parse a disposition + * header which is typically sent as part of a multipart upload. It + * can be used to determine the type of the upload. + */ + public ContentDispositionParser() { + this.file = new ParseBuffer(); + this.name = new ParseBuffer(); + this.skip = new ParseBuffer(); + } + + /** + * Constructor for the <code>ContentDispositionParser</code> object. + * This is used to create a parser that can parse a disposition header + * which is typically sent as part of a multipart upload. It can + * be used to determine the type of the upload. + * + * @param text this is the header value that is to be parsed + */ + public ContentDispositionParser(String text) { + this(); + parse(text); + } + + /** + * This method is used to acquire the file name of the part. This + * is used when the part represents a text parameter rather than + * a file. However, this can also be used with a file part. + * + * @return this returns the file name of the associated part + */ + public String getFileName() { + return file.toString(); + } + + /** + * This method is used to acquire the name of the part. Typically + * this is used when the part represents a text parameter rather + * than a file. However, this can also be used with a file part. + * + * @return this returns the name of the associated part + */ + public String getName() { + return name.toString(); + } + + /** + * This method is used to determine the type of a part. Typically + * a part is either a text parameter or a file. If this is true + * then the content represented by the associated part is a file. + * + * @return this returns true if the associated part is a file + */ + public boolean isFile() { + return !form || file.length() > 0; + } + + /** + * This will initialize the <code>Parser</code> when it is ready + * to parse a new <code>String</code>. This will reset the + * parser to a ready state. This method is invoked by the parser + * before the parse method is invoked, it is used to pack the + * contents of the header and clear any previous tokens used. + */ + protected void init() { + if(count > 0) { + pack(); + } + clear(); + } + /** + * This is used to clear all previously collected tokens. This + * allows the parser to be reused when there are multiple source + * strings to be parsed. Clearing of the tokens is performed + * when the parser is initialized. + */ + protected void clear() { + file.clear(); + name.clear(); + form = false; + off = 0; + } + + /** + * This is the method that should be implemented to read the + * buffer. This method will extract the type from the header and + * the tries to extract the optional parameters if they are in + * the header. The optional parts are the file name and name. + */ + protected void parse() { + type(); + parameters(); + } + + /** + * This is used to remove all whitespace characters from the + * <code>String</code> excluding the whitespace within literals. + * The definition of a literal can be found in RFC 2616. + * <p> + * The definition of a literal for RFC 2616 is anything between 2 + * quotes but excuding quotes that are prefixed with the backward + * slash character. + */ + private void pack() { + char old = buf[0]; + int len = count; + int seek = 0; + int pos = 0; + + while(seek < len){ + char ch = buf[seek++]; + + if(ch == '"' && old != '\\'){ /* qd-text*/ + buf[pos++] = ch; + + while(seek < len){ + old = buf[seek-1]; + ch = buf[seek++]; + buf[pos++] = ch; + + if(ch =='"'&& old!='\\'){ /*qd-text*/ + break; + } + } + }else if(!space(ch)){ + old = buf[seek - 1]; + buf[pos++] = old; + } + } + count = pos; + } + + /** + * This is used to determine the type of the disposition header. This + * will allow the parser to determine it the header represents form + * data or a file upload. Once it determines the type of the upload + * header it sets an internal flag which can be used. + */ + private void type() { + if(skip("form-data")) { + form = true; + } else if(skip("file")) { + form = false; + } + } + + /** + * This will read the parameters from the header value. This will search + * for the <code>filename</code> parameter within the set of parameters + * which are given to the type. The <code>filename</code> param and the + * the <code>name</code> are tokenized by this method. + */ + private void parameters(){ + while(skip(";")){ + if(skip("filename=")){ + value(file); + } else { + if(skip("name=")) { + value(name); + } else { + parameter(); + } + } + } + } + + /** + * This will read the parameters from the header value. This will search + * for the <code>filename</code> parameter within the set of parameters + * which are given to the type. The <code>filename</code> param and the + * the <code>name</code> are tokenized by this method. + */ + private void parameter() { + name(); + off++; + value(skip); + } + + /** + * This will simply read all characters from the buffer before the first '=' + * character. This represents a parameter name (see RFC 2616 for token). The + * parameter name is not buffered it is simply read from the buffer. This will + * not cause an <code>IndexOutOfBoundsException</code> as each offset + * is checked before it is acccessed. + */ + private void name(){ + while(off < count){ + if(buf[off] =='='){ + break; + } + off++; + } + } + + /** + * This is used to read a parameters value from the buf. This will read all + * <code>char</code>'s upto but excluding the first terminal <code>char</code> + * encountered from the off within the buf, or if the value is a literal + * it will read a literal from the buffer (literal is any data between + * quotes except if the quote is prefixed with a backward slash character). + * + * @param value this is the parse buffer to append the value to + */ + private void value(ParseBuffer value) { + if(quote(buf[off])) { + char quote = buf[off]; + + for(off++; off < count;) { + if(quote == buf[off]) { + if(buf[++off - 2] != '\\') { + break; + } + } + + value.append(buf[off++]); + } + } else { + while(off < count) { + if(buf[off] == ';') { + break; + } + + value.append(buf[off]); + off++; + } + } + } + + /** + * This method is used to determine if the specified character is a quote + * character. The quote character is typically used as a boundary for the + * values within the header. This accepts a single or double quote. + * + * @param ch the character to determine if it is a quotation + * + * @return true if the character provided is a quotation character + */ + private boolean quote(char ch) { + return ch == '\'' || ch == '"'; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/ContentTypeParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/ContentTypeParser.java new file mode 100644 index 0000000..f42c073 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/ContentTypeParser.java @@ -0,0 +1,556 @@ +/* + * ContentTypeParser.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import org.simpleframework.common.KeyMap; +import org.simpleframework.common.parse.ParseBuffer; +import org.simpleframework.common.parse.Parser; +import org.simpleframework.http.ContentType; + +/** + * This provides access to the MIME type parts, that is the primary + * type, the secondary type and an optional character set parameter. + * The <code>charset</code> parameter is one of many parameters that + * can be associated with a MIME type. This however this exposes this + * parameter with a typed method. + * <p> + * The <code>getCharset</code> will return the character encoding the + * content type is encoded within. This allows the user of the content + * to decode it correctly. Other parameters can be acquired from this + * by simply providing the name of the parameter. + * + * @author Niall Gallagher + */ +public class ContentTypeParser extends Parser implements ContentType { + + /** + * Used to store the characters consumed for the secondary type. + */ + private ParseBuffer secondary; + + /** + * Used to store the characters consumed for the primary type. + */ + private ParseBuffer primary; + + /** + * Used to store the characters for the charset parameter. + */ + private ParseBuffer charset; + + /** + * Used to store the characters consumed for the type. + */ + private ParseBuffer type; + + /** + * Used to collect the name of a content type parameter. + */ + private ParseBuffer name; + + /** + * Used to collect the value of the content type parameter. + */ + private ParseBuffer value; + + /** + * Used to store the name value pairs of the parameters. + */ + private KeyMap<String> map; + + /** + * The default constructor will create a <code>ContentParser</code> + * that contains no charset, primary or secondary. This can be used + * to extract the primary, secondary and the optional charset + * parameter by using the parser's <code>parse(String)</code> + * method. + */ + public ContentTypeParser(){ + this.secondary = new ParseBuffer(); + this.primary = new ParseBuffer(); + this.charset = new ParseBuffer(); + this.value = new ParseBuffer(); + this.type = new ParseBuffer(); + this.name = new ParseBuffer(); + this.map = new KeyMap<String>(); + } + + /** + * This is primarily a convenience constructor. This will parse + * the <code>String</code> given to extract the MIME type. This + * could be achieved by calling the default no-arg constructor + * and then using the instance to invoke the <code>parse</code> + * method on that <code>String</code>. + * + * @param header <code>String</code> containing a MIME type value + */ + public ContentTypeParser(String header){ + this(); + parse(header); + } + + /** + * This method is used to get the primary and secondary parts + * joined together with a "/". This is typically how a content + * type is examined. Here convenience is most important, we can + * easily compare content types without any parameters. + * + * @return this returns the primary and secondary types + */ + public String getType() { + return type.toString(); + } + + /** + * This sets the primary type to whatever value is in the string + * provided is. If the string is null then this will contain a + * null string for the primary type of the parameter, which is + * likely invalid in most cases. + * + * @param value the type to set for the primary type of this + */ + public void setPrimary(String value) { + type.reset(value); + type.append('/'); + type.append(secondary); + primary.reset(value); + } + + /** + * This is used to retrieve the primary type of this MIME type. The + * primary type part within the MIME type defines the generic type. + * For example <code>text/plain; charset=UTF-8</code>. This will + * return the text value. If there is no primary type then this + * will return <code>null</code> otherwise the string value. + * + * @return the primary type part of this MIME type + */ + public String getPrimary() { + return primary.toString(); + } + + /** + * This sets the secondary type to whatever value is in the string + * provided is. If the string is null then this will contain a + * null string for the secondary type of the parameter, which is + * likely invalid in most cases. + * + * @param value the type to set for the primary type of this + */ + public void setSecondary(String value) { + type.reset(primary); + type.append('/'); + type.append(value); + secondary.reset(value); + } + + /** + * This is used to retrieve the secondary type of this MIME type. + * The secondary type part within the MIME type defines the generic + * type. For example <code>text/html; charset=UTF-8</code>. This + * will return the HTML value. If there is no secondary type then + * this will return <code>null</code> otherwise the string value. + * + * @return the primary type part of this MIME type + */ + public String getSecondary(){ + return secondary.toString(); + } + + /** + * This will set the <code>charset</code> to whatever value the + * string contains. If the string is null then this will not set + * the parameter to any value and the <code>toString</code> method + * will not contain any details of the parameter. + * + * @param enc parameter value to add to the MIME type + */ + public void setCharset(String enc) { + charset.reset(enc); + } + + /** + * This is used to retrieve the <code>charset</code> of this MIME + * type. This is a special parameter associated with the type, if + * the parameter is not contained within the type then this will + * return null, which typically means the default of ISO-8859-1. + * + * @return the value that this parameter contains + */ + public String getCharset() { + return charset.toString(); + } + + /** + * This is used to retrieve an arbitrary parameter from the MIME + * type header. This ensures that values for <code>boundary</code> + * or other such parameters are not lost when the header is parsed. + * This will return the value, unquoted if required, as a string. + * + * @param name this is the name of the parameter to be retrieved + * + * @return this is the value for the parameter, or null if empty + */ + public String getParameter(String name) { + return map.get(name); + } + + /** + * This will add a named parameter to the content type header. If + * a parameter of the specified name has already been added to the + * header then that value will be replaced by the new value given. + * Parameters such as the <code>boundary</code> as well as other + * common parameters can be set with this method. + * + * @param name this is the name of the parameter to be added + * @param value this is the value to associate with the name + */ + public void setParameter(String name, String value) { + map.put(name, value); + } + + /** + * This will initialize the parser when it is ready to parse + * a new <code>String</code>. This will reset the parser to a + * ready state. The init method is invoked by the parser when + * the <code>Parser.parse</code> method is invoked. + */ + protected void init(){ + if(count > 0) { + pack(); + } + clear(); + } + + /** + * This is used to clear all previously collected tokens. This + * allows the parser to be reused when there are multiple source + * strings to be parsed. Clearing of the tokens is performed + * when the parser is initialized. + */ + private void clear() { + primary.clear(); + secondary.clear(); + charset.clear(); + name.clear(); + value.clear(); + type.clear(); + map.clear(); + off = 0; + } + + /** + * Reads and parses the MIME type from the given <code>String</code> + * object. This uses the syntax defined by RFC 2616 for the media-type + * syntax. This parser is only concerned with one parameter, the + * <code>charset</code> parameter. The syntax for the media type is + * <pre> + * media-type = token "/" token *( ";" parameter ) + * parameter = token | literal + * </pre> + */ + protected void parse(){ + primary(); + off++; + secondary(); + parameters(); + } + + /** + * This is used to remove all whitespace characters from the + * <code>String</code> excluding the whitespace within literals. + * The definition of a literal can be found in RFC 2616. + * <p> + * The definition of a literal for RFC 2616 is anything between 2 + * quotes but excluding quotes that are prefixed with the backward + * slash character. + */ + private void pack() { + char old = buf[0]; + int len = count; + int seek = 0; + int pos = 0; + + while(seek < len){ + char ch = buf[seek++]; + + if(ch == '"' && old != '\\'){ /* qd-text*/ + buf[pos++] = ch; + + while(seek < len){ + old = buf[seek-1]; + ch = buf[seek++]; + buf[pos++] = ch; + + if(ch =='"'&& old!='\\'){ /*qd-text*/ + break; + } + } + }else if(!space(ch)){ + old = buf[seek - 1]; + buf[pos++] = old; + } + } + count = pos; + } + + /** + * This reads the type from the MIME type. This will fill the + * type <code>ParseBuffer</code>. This will read all chars + * upto but not including the first instance of a '/'. The type + * of a media-type as defined by RFC 2616 is + * <code>type/subtype;param=val;param2=val</code>. + */ + private void primary(){ + while(off < count){ + if(buf[off] =='/'){ + type.append('/'); + break; + } + type.append(buf[off]); + primary.append(buf[off]); + off++; + } + } + + /** + * This reads the subtype from the MIME type. This will fill the + * subtype <code>ParseBuffer</code>. This will read all chars + * upto but not including the first instance of a ';'. The subtype + * of a media-type as defined by RFC 2616 is + * <code>type/subtype;param=val;param2=val</code>. + */ + private void secondary(){ + while(off < count){ + if(buf[off] ==';'){ + break; + } + type.append(buf[off]); + secondary.append(buf[off]); + off++; + } + } + + /** + * This will read the parameters from the MIME type. This will search + * for the <code>charset</code> parameter within the set of parameters + * which are given to the type. The <code>charset</code> param is the + * only parameter that this parser will tokenize. + * <p> + * This will remove any parameters that preceed the charset parameter. + * Once the <code>charset</code> is retrived the MIME type is considered + * to be parsed. + */ + private void parameters(){ + while(skip(";")){ + if(skip("charset=")){ + charset(); + break; + }else{ + parameter(); + insert(); + } + } + } + + /** + * This will add the name and value tokens to the parameters map. + * If any previous value of the given name has been inserted + * into the map then this will overwrite that value. This is + * used to ensure that the string value is inserted to the map. + */ + private void insert() { + insert(name, value); + name.clear(); + value.clear(); + } + + /** + * This will add the given name and value to the parameters map. + * If any previous value of the given name has been inserted + * into the map then this will overwrite that value. This is + * used to ensure that the string value is inserted to the map. + * + * @param name this is the name of the value to be inserted + * @param value this is the value of a that is to be inserted + */ + private void insert(ParseBuffer name, ParseBuffer value) { + map.put(name.toString(), value.toString()); + } + + /** + * This is a parameter as defined by RFC 2616. The parameter is added to a + * MIME type e.g. <code>type/subtype;param=val</code> etc. The parameter + * name and value are not stored. This is used to simply update the read + * offset past the parameter. The reason for reading the parameters is to + * search for the <code>charset</code> parameter which will indicate the + * encoding. + */ + private void parameter(){ + name(); + off++; /* = */ + value(); + } + + /** + * This will simply read all characters from the buffer before the first '=' + * character. This represents a parameter name (see RFC 2616 for token). The + * parameter name is not buffered it is simply read from the buffer. This will + * not cause an <code>IndexOutOfBoundsException</code> as each offset + * is checked before it is acccessed. + */ + private void name(){ + while(off < count){ + if(buf[off] =='='){ + break; + } + name.append(buf[off]); + off++; + } + } + + /** + * This is used to read a parameters value from the buf. This will read all + * <code>char</code>'s upto but excluding the first terminal <code>char</code> + * encountered from the off within the buf, or if the value is a literal + * it will read a literal from the buffer (literal is any data between + * quotes except if the quote is prefixed with a backward slash character). + */ + private void value(){ + if(quote(buf[off])){ + for(off++; off < count;){ + if(quote(buf[off])){ + if(buf[++off-2]!='\\'){ + break; + } + } + value.append(buf[off++]); + } + }else{ + while(off < count){ + if(buf[off] ==';') { + break; + } + value.append(buf[off]); + off++; + } + } + } + + /** + * This method is used to determine if the specified character is a quote + * character. The quote character is typically used as a boundary for the + * values within the header. This accepts a single or double quote. + * + * @param ch the character to determine if it is a quotation + * + * @return true if the character provided is a quotation character + */ + private boolean quote(char ch) { + return ch == '\'' || ch == '"'; + } + + /** + * This is used to read the value from the <code>charset</code> param. + * This will fill the <code>charset</code> <code>ParseBuffer</code> and with + * the <code>charset</code> value. This will read a literal or a token as + * the <code>charset</code> value. If the <code>charset</code> is a literal + * then the quotes will be read as part of the charset. + */ + private void charset(){ + if(buf[off] == '"'){ + charset.append('"'); + for(off++; off < count;){ + charset.append(buf[off]); + if(buf[off++]=='"') + if(buf[off-2]!='\\'){ + break; + } + } + }else{ + while(off < count){ + if(buf[off]==';') { + break; + } + charset.append(buf[off]); + off++; + } + } + } + + /** + * This will return the value of the MIME type as a string. This + * will concatenate the primary and secondary type values and + * add the <code>charset</code> parameter to the type which will + * recreate the content type. + * + * @return this returns the string representation of the type + */ + private String encode() { + StringBuilder text = new StringBuilder(); + + if(primary != null) { + text.append(primary); + text.append("/"); + text.append(secondary); + } + if(charset.length() > 0) { + text.append("; charset="); + text.append(charset); + } + return encode(text); + } + + /** + * This will return the value of the MIME type as a string. This + * will concatenate the primary and secondary type values and + * add the <code>charset</code> parameter to the type which will + * recreate the content type. + * + * @param text this is the buffer to encode the parameters to + * + * @return this returns the string representation of the type + */ + private String encode(StringBuilder text) { + for(String name : map) { + String value = map.get(name); + + text.append("; "); + text.append(name); + + if(value != null) { + text.append("="); + text.append(value);; + } + } + return text.toString(); + } + + /** + * This will return the value of the MIME type as a string. This + * will concatenate the primary and secondary type values and + * add the <code>charset</code> parameter to the type which will + * recreate the content type. + * + * @return this returns the string representation of the type + */ + public String toString() { + return encode(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/CookieParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/CookieParser.java new file mode 100644 index 0000000..1d07c04 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/CookieParser.java @@ -0,0 +1,589 @@ +/* + * CookieParser.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import org.simpleframework.common.parse.Parser; +import org.simpleframework.http.Cookie; + +import java.util.Iterator; + +/** + * CookieParser is used to parse the cookie header. The cookie header is + * one of the headers that is used by the HTTP state management mechanism. + * The Cookie header is the header that is sent from the client to the + * server in response to a Set-Cookie header. The syntax of the Cookie + * header as taken from RFC 2109, HTTP State Management Mechanism. + * <pre> + * + * cookie = "Cookie:" cookie-version + * 1*((";" | ",") cookie-value) + * cookie-value = NAME "=" VALUE [";" path] [";" domain] + * cookie-version = "$Version" "=" value + * NAME = attr + * VALUE = value + * path = "$Path" "=" value + * domain = "$Domain" "=" value + * + * </pre> + * The cookie header may consist of several cookies. Each cookie can be + * extracted from the header by examining the it syntax of the cookie + * header. The syntax of the cookie header is defined in RFC 2109. + * <p> + * Each cookie has a <code>$Version</code> attribute followed by multiple + * cookies. Each contains a name and a value, followed by an optional + * <code>$Path</code> and <code>$Domain</code> attribute. This will parse + * a given cookie header and return each cookie extracted as a + * <code>Cookie</code> object. + * + * @author Niall Gallagher + */ +public class CookieParser extends Parser implements Iterable<Cookie> { + + /** + * Determines when the <code>Parser</code> has finished. + */ + private boolean finished; + + /** + * Used so the <code>Parser</code> does not parse twice. + */ + private boolean parsed; + + /** + * Version of the <code>Cookie</code> being parsed. + */ + private int version; + + /** + * Used to store the name of the <code>Cookie</code>. + */ + private Token name; + + /** + * Used to store the value of the <code>Cookie</code>. + */ + private Token value; + + /** + * Used to store the <code>$Path</code> values. + */ + private Token path; + + /** + * Used to store the <code>$Domain</code> values. + */ + private Token domain; + + /** + * Create a <code>CookieParser</code> that contains no cookies. + * the instance will return <code>false</code> for the + * <code>hasNext</code> method. cookies may be parsed using + * this instance by using the <code>parse</code> method. + */ + public CookieParser(){ + this.path = new Token(); + this.domain = new Token(); + this.name = new Token(); + this.value = new Token(); + this.finished = true; + } + + /** + * This is primarily a convineance constructor. This will parse the + * <code>String</code> given to extract the cookies. This could be + * achived by calling the default no-arg constructor and then using + * the instance to invoke the <code>parse</code> method on that + * <code>String</code>. + * + * @param header a <code>String</code> containing a cookie value + */ + public CookieParser(String header){ + this(); + parse(header); + } + + /** + * Resets the cookie and the buffer variables for this + * <code>CookieParser</code>. It is used to set the + * state of the parser to start parsing a new cookie. + */ + protected void init() { + finished = false; + parsed =false; + version = 0; + off = 0; + version(); + } + + /** + * This will extract the next <code>Cookie</code> from the + * buffer. If all the characters in the buffer have already + * been examined then this method will simply do nothing. + * Otherwise this will parse the remainder of the buffer + * and (if it follows RFC 2109) produce a <code>Cookie</code>. + */ + protected void parse() { + if(!finished){ + cookie(); + parsed=true; + } + } + + /** + * This is used to skip an arbitrary <code>String</code> within the + * <code>char</code> buf. It checks the length of the <code>String</code> + * first to ensure that it will not go out of bounds. A comparison + * is then made with the buffers contents and the <code>String</code> + * if the reigon in the buffer matched the <code>String</code> then the + * offset within the buffer is increased by the <code>String</code>'s + * length so that it has effectively skipped it. + * <p> + * This <code>skip</code> method will ignore all of the whitespace text. + * This will also skip trailing spaces within the the input text and + * all spaces within the source text. For example if the input was + * the string "s omete xt" and the source was "some text to skip" then + * the result of a skip ignoring spaces would be "to skip" in the + * source string, as the trailing spaces are also eaten by this. + * + * @param text this is the <code>String</code> value to be skipped + * + * @return true if the <code>String</code> was skipped + */ + protected boolean skip(String text){ + int size = text.length(); + int seek = off; + int read = 0; + + if(off + size > count){ + return false; + } + while(read < size) { + char a = text.charAt(read); + char b = buf[seek]; + + if(space(b)){ + if(++seek >= count){ + return false; + } + }else if(space(a)){ + if(++read >= size) { + continue; + } + }else { + if(toLower(a) != toLower(b)){ + return false; + } + read++; + seek++; + } + } + for(off = seek; off < count; off++){ + if(!space(buf[off])) + break; + } + return true; + } + + /** + * This is used to acquire the cookie values from the provided + * the provided source text. This allows the cookie parser to be + * used within a for each loop to parse out the values of a + * cookie one by one so that they may be used or stored. + * + * @return this returns an iterator for extracting cookie value + */ + public Iterator<Cookie> iterator() { + return new Sequence(); + } + + /** + * This is used so that the collection of <code>Cookies</code> + * can be reiterated. This allows the collection to be reused. + * The <code>reset</code> method will invoke the super classes + * <code>init</code> method. This will reinitialize this + * <code>Parser</code> so the cookie will be reparsed. + */ + public void reset() { + init(); + parse(); + } + + /** + * Creates the <code>Cookie</code> from the token objects. It is + * assumed that the <code>Cookie</code> <code>String</code> has + * been parsed when this is called. This should only be used after + * the <code>parse</code> method has been called. + * <p> + * If there is no <code>$Domain</code> or <code>$Path</code> + * within the <code>Cookie</code> <code>String</code> then the + * <code>getDomain</code> and <code>getPath</code> are null. + * + * @return the <code>Cookie</code> that was just parsed + */ + private Cookie getCookie() { + return getCookie(name.toString(), + value.toString()); + } + + /** + * Creates the <code>Cookie</code> from the token objects. It is + * assumed that the <code>Cookie</code> <code>String</code> has + * been parsed when this is called. This should only be used after + * the <code>parse</code> method has been called. + * <p> + * If there is no <code>$Domain</code> or <code>$Path</code> + * within the <code>Cookie</code> <code>String</code> then the + * <code>getDomain</code> and <code>getPath</code> are null. + * + * @param name the name that the <code>Cookie</code> contains + * @param value the value that the <code>Cookie</code> contains + * + * @return the <code>Cookie</code> that was just parsed + */ + private Cookie getCookie(String name, String value) { + Cookie cookie = new Cookie(name, value, false); + + if(domain.len > 0) { + cookie.setDomain(domain.toString()); + } + if(path.len > 0) { + cookie.setPath(path.toString()); + } + cookie.setVersion(version); + return cookie; + } + + /** + * This is used to parse a <code>Cookie</code> from the buffer + * that contains the <code>Cookie</code> values. This will first + * try to remove any trailing value after the version/prev + * <code>Cookie</code> once this is removed it will extract the + * name/value pair from the <code>Cookie</code>. The name and + * value of the <code>Cookie</code> will be saved by the name + * and value tokens. + */ + private void cookie(){ + if(!skip(",")){ /* ,|; */ + skip(";"); + } + name(); + skip("="); /* = */ + value(); + } + + /** + * This initializes the name token and extracts the name of this + * <code>Cookie</code>. The offset and length of the name will be + * saved in the name token. This will read all <code>char</code>'s + * upto but excluding the first '=' <code>char</code> encountered + * from the <code>off</code> within the buffer. + */ + private void name() { + name.off = off; + name.len = 0; + while(off < count){ + if(buf[off] == '='){ + break; + } + name.len++; + off++; + } + } + + /** + * Used to extract everything found after the <code>NAME '='</code> + * within a <code>Cookie</code>. This extracts the <code>Cookie</code> + * value the <code>$Path</code> and <code>$Domain</code> attributes + * if they exist (i.e. <code>$Path</code> and <code>$Domain</code> + * are optional in a cookie see RFC 2109). + * <p> + * The path method reads the terminal found before it as does the + * <code>domain</code> method that is ";$Path" is read as the first + * part of the path method. This is because if there is no path the + * parser should not read data it does not know belongs to a specific + * part of the <code>Cookie</code>. + */ + private void value() { + data(); + path(); + domain(); + } + + /** + * This initializes the value token and extracts the value of this + * <code>Cookie</code>. The offset and length of the value will be + * saved in the value token. This will read all <code>char</code>'s + * upto but excluding the first terminal char encountered from the + * off within the buffer, or if the value is a literal it will read + * a literal from the buffer (literal is any data between quotes + * except if the quote is prefixed with a backward slash character + * that is '\'). + */ + private void data() { + value.off = off; + value.len = 0; + if(off < count && buf[off] == '"'){ + value.len++; + for(off++; off < count;){ + value.len++; + if(buf[off++]=='"') + if(buf[off-2]!='\\'){ + break; + } + } + value.len-=2; /* remove " */ + value.off++; /* remove " */ + }else { + while(off < count){ + if(terminal(buf[off])) + break; + value.len++; + off++; + } + } + } + + /** + * This initializes the path token and extracts the <code>$Path</code> + * of this <code>Cookie</code>. The offset and length of the path will + * be saved in the path token. This will read all <code>char</code>'s + * up to but excluding the first terminal <code>char</code> encountered + * from the <code>off</code> within the buffer, or if the value is a + * literal it will read a literal from the buffer (literal is any data + * between quotes except if the quote is prefixed with a backward slash + * character, that is '\'). + * <p> + * This reads the terminal before the <code>$Path</code> so that if + * there is no <code>$Path</code> for the <code>Cookie</code> then + * the character before it will not be read needlessly. + */ + private void path() { + path.len = 0; /* reset */ + if(skip(";$Path=")){ + path.off = off; + if(buf[off] == '"'){ + path.len++; + for(off++; off < count;){ + path.len++; + if(buf[off++]=='"') + if(buf[off-2]!='\\'){ + break; + } + } + path.len-=2; /* remove " */ + path.off++; /* remove " */ + }else{ + while(off < count){ + if(terminal(buf[off])) + break; + path.len++; + off++; + } + } + } + } + + /** + * Initializes the domain token and extracts the <code>$Domain</code> + * of this <code>Cookie</code>. The offset and length of the domain + * will be saved in the path token. This will read all characters up + * to but excluding the first terminal <code>char</code> encountered + * from the off within the buffer, or if the value is a literal it + * will read a literal from the buffer (literal is any data between + * quotes except if the quote is prefixed with a backward slash + * character, that is '\'). + * <p> + * This reads the terminal before the <code>$Domain</code> so that + * if there is no <code>$Domain</code> for the <code>Cookie</code> + * then the character before it will not be read needlessly. + */ + private void domain(){ + domain.len = 0; /* reset */ + if(skip(";$Domain=")) { + domain.off = off; + if(buf[off] == '"'){ + domain.len++; + for(off++; off < count;){ + domain.len++; + if(buf[off++]=='"') + if(buf[off-2]!='\\'){ + break; + } + } + domain.len-=2; /* remove " */ + domain.off++; /* remove " */ + }else{ + while(off < count){ + if(terminal(buf[off])) + break; + domain.len++; + off++; + } + } + } + } + + /** + * This extracts the <code>$Version</code> of this <code>Cookie</code>. + * The version is parsed and converted into a decimal int from the digit + * characters that make up a version. + * <p> + * This will read all digit <code>char</code>'s up to but excluding the + * first non digit <code>char</code> that it encounters from the offset + * within the buffer, or if the value is a literal it will read a literal + * from the buffer (literal is any data between quotes except if the quote + * is prefixed with a backward slash character i.e. '\'). + */ + private void version(){ + if(skip("$Version=")) { + if(buf[off] == '"'){ + off++; + } + while(off < count){ + if(!digit(buf[off])){ + break; + } + version *= 10; + version += buf[off]; + version -= '0'; + off++; + } + if(buf[off] == '"'){ + off++; + } + }else{ + version = 1; + } + } + + /** + * This is used to determine if a given iso8859-1 character is + * a terminal character. That is either the ';' or ',' + * characters. Although the RFC 2109 says the terminal can be + * either a comma, it is not used by any browsers. + * + * @param ch the character that is to be compared + * + * @return true if this is a semicolon character + */ + private boolean terminal(char ch) { + return ch == ';'; + } + + /** + * This is used to represent an <code>Iterator</code> that will + * iterate over the available cookies within the provided source + * text. This allows the cookie parser to be used as an iterable + * with for each loops. Cookies can not be removed with this. + */ + private class Sequence implements Iterator<Cookie> { + + /** + * Extracts the next <code>Cookie</code> object from the string + * given. This will return <code>null</code> when there are no + * more cookies left in the <code>String</code> being parsed. + * <p> + * To find out when there are no more cookies left use the + * <code>hasNext</code> method. This will only set the name, + * value, path, domain name version of the <code>cookie</code> + * because as of RFC 2109 these are the only attributes a + * <code>Cookie</code> may have, the path and domain are + * optional. + * + * @return an initialized <code>Cookie</code> object + */ + public Cookie next(){ + if(!hasNext()) { + return null; + } + parsed = false; + return getCookie(); + } + + + /** + * Determine whether or not there are any <code>Cookie</code>s + * left in the <code>String</code>. This will attempt to extract + * another <code>Cookie</code> from the <code>String</code> and + * cache the result so the <code>next</code> method will produce + * this <code>Cookie</code>. If another <code>Cookie</code> cannot + * be parsed from the remainder of the <code>String</code> then + * this will return <code>false</code> otherwise it will return + * <code>true</code>. + * + * @return true if there are more cookies false otherwise + */ + public boolean hasNext(){ + if(finished) { + return false; + } + if(parsed) { + return true; + } + parse(); + + if(name.len <=0){ + finished = true; + return false; + } + return true; + + } + + /** + * This method is used to remove items from the iterator. This + * however performs no action as the act of parsing should not + * modify the underlying source text value so that it can be + * reset with the <code>reset</code> method and used again. + */ + public void remove() { + return; + } + } + + /** + * This is a token object that is used to store the offset and + * length of a region of chars in the <code>CookieParser.buf</code> + * array. The <code>toString</code> method of this token will + * produce the <code>String</code> value of the region it + * represents. + */ + private class Token { + + /** + * The numer of characters that were consumed by this token. + */ + public int len; + + /** + * The offset within the buffer that this token starts from. + */ + public int off; + + /** + * This converts region within the buffer to a <code>String</code>. + * This converts the region only if there is a sufficient length. + * + * @return the <code>String</code> value of the region + */ + public String toString(){ + return new String(buf,off,len); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/DateParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/DateParser.java new file mode 100644 index 0000000..7efea9c --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/DateParser.java @@ -0,0 +1,642 @@ +/* + * DateParser.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import static java.util.Calendar.DAY_OF_MONTH; +import static java.util.Calendar.DAY_OF_WEEK; +import static java.util.Calendar.HOUR_OF_DAY; +import static java.util.Calendar.MILLISECOND; +import static java.util.Calendar.MINUTE; +import static java.util.Calendar.MONTH; +import static java.util.Calendar.SECOND; +import static java.util.Calendar.YEAR; + +import java.util.Calendar; +import java.util.TimeZone; + +import org.simpleframework.common.parse.Parser; + +/** + * This is used to create a <code>Parser</code> for the HTTP date format. + * This will parse the 3 formats that are acceptable for the HTTP/1.1 date. + * The three formats that are acceptable for the HTTP-date are + * <pre> + * Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + * </pre> + * <p> + * This can also parse the date in ms as retrived from the <code>System</code>'s + * <code>System.currentTimeMillis</code> method. This has a parse method for a + * <code>long</code> which will do the same as the <code>parse(String)</code>. + * Once the date has been parsed there are two methods that allow the date + * to be represented, the <code>toLong</code> method converts the date to a + * <code>long</code> and the <code>toString</code> method will convert the date + * into a <code>String</code>. + * <p> + * This produces the same string as the <code>SimpleDateFormat.format</code> + * using the pattern <code>"EEE, dd MMM yyyy hh:mm:ss 'GMT'"</code>. This will + * however do the job faster as it does not take arbitrary inputs. + * + * @author Niall Gallagher + */ +public class DateParser extends Parser { + + /** + * Ensure that the time zone for dates if set to GMT. + */ + private static final TimeZone ZONE = TimeZone.getTimeZone("GMT"); + + /** + * Contains the possible days of the week for RFC 1123. + */ + private static final String WKDAYS[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; + + /** + * Contains the possible days of the week for RFC 850. + */ + private static final String WEEKDAYS[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; + + /** + * Contains the possible months in the year for HTTP-date. + */ + private static final String MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + + /** + * Used as an index into the months array to get the month. + */ + private int month; + + /** + * Represents the decimal value of the date such as 1977. + */ + private int year; + + /** + * Represents the decimal value of the date such as 18. + */ + private int day; + + /** + * Used as an index into the weekdays array to get the weekday. + */ + private int weekday; + + /** + * Represents the decimal value of the hour such as 24. + */ + private int hour; + + /** + * Represents the decimal value of the minute. + */ + private int mins; + + /** + * Represents the decimal value for the second. + */ + private int secs; + + /** + * The default constructor will create a parser that can parse + * <code>String</code>s that contain dates in the form of RFC 1123, + * RFC 850 or asctime. If the dates that are to be parsed are not in + * the form of one of these date encodings the results of this + * parser will be random. + */ + public DateParser(){ + this.init(); + } + + /** + * This constructor will conveniently parse the <code>long</code> argument + * in the constructor. This can also be done by first calling the no-arg + * constructor and then using the parse method. + * <p> + * This will then set this object to one that uses the RFC 1123 format + * for a date. + * + * @param date the date to be parsed + */ + public DateParser(long date){ + this(); + parse(date); + } + + /** This constructor will conveniently parse the <code>String</code> + * argument in the constructor. This can also be done by first calling + * the no-arg constructor and then using the parse method. + * <p> + * This will then set this object to one that uses the RFC 1123 format + * for a date. + * + * @param date the date to be parsed + */ + public DateParser(String date) { + this(); + parse(date); + } + + /** + * This is used to extract the date from a <code>long</code>. If this + * method is given the value of the date as a <code>long</code> it will + * construct the RFC 1123 date as required by RFC 2616 sec 3.3. + * <p> + * This saves time on parsing a <code>String</code> that is encoded in + * the HTTP-date format. The date given must be positive, if the date + * given is not a positive '<code>long</code>' then the results + * of this method is random/unknown. + * + * @param date the date to be parsed + */ + public void parse(long date){ + Calendar calendar = Calendar.getInstance(ZONE); + calendar.setTimeInMillis(date); + + weekday = calendar.get(DAY_OF_WEEK); + year = calendar.get(YEAR); + month = calendar.get(MONTH); + day = calendar.get(DAY_OF_MONTH); + hour = calendar.get(HOUR_OF_DAY); + mins = calendar.get(MINUTE); + secs = calendar.get(SECOND); + month = month > 11 ? 11: month; + weekday = (weekday+5) % 7; + } + + /** + * Convenience method used to convert the specified HTTP date in to a + * long representing the time. This is used when a single method is + * required to convert a HTTP date format to a usable long value for + * use in creating <code>Date</code> objects. + * + * @param date the date specified in on of the HTTP date formats + * + * @return the date value as a long value in milliseconds + */ + public long convert(String date) { + parse(date); + return toLong(); + + } + + /** + * Convenience method used to convert the specified long date in to a + * HTTP date format. This is used when a single method is required to + * convert a long data value in milliseconds to a HTTP date value. + * + * @param date the date specified as a long of milliseconds + * + * @return the date represented in the HTTP date format RFC 1123 + */ + public String convert(long date) { + parse(date); + return toString(); + } + + /** + * This is used to reset the date and the buffer variables + * for this <code>DateParser</code>. Every in is set to the + * value of 0. + */ + protected void init() { + month = year = day = + weekday = hour = mins = + secs = off = 0; + } + + /** + * This is used to parse the contents of the <code>buf</code>. This + * checks the fourth char of the buffer to see what it contains. Invariably + * a date format belonging to RFC 1123 will have a ',' character in position 4, + * a date format belonging to asctime will have a ' ' character in position 4 + * and if neither of these characters are found at position 4 then it is + * assumed that the date is in the RFC 850 fromat, however it may not be. + */ + protected void parse(){ + if(buf.length<4)return; + if(buf[3]==','){ + rfc1123(); + }else if(buf[3]==' '){ + asctime(); + }else{ + rfc850(); + } + } + + /** + * This will parse a date that is in the form of an RFC 1123 date. This + * date format is the date format that is to be used with all applications + * that are HTTP/1.1 compliant. The RFC 1123 date format is + * <pre> + * rfc1123 = 'wkday "," SP date1 SP time SP GMT'. + * date1 = '2DIGIT SP month SP 4DIGIT' and finally + * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT'. + * </pre> + */ + private void rfc1123(){ + wkday(); + off+=2; + date1(); + off++; + time(); + } + + /** + * This will parse a date that is in the form of an RFC 850 date. This date + * format is the date format that is to be used with some applications that + * are HTTP/1.0 compliant. The RFC 1123 date format is + * <pre> + * rfc850 = 'weekday "," SP date2 SP time SP GMT'. + * date2 = '2DIGIT "-" month "-" 2DIGIT' and finally + * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT'. + * </pre> + */ + private void rfc850() { + weekday(); + off+=2; + date2(); + off++; + time(); + } + + /** + * This will parse a date that is in the form of an asctime date. This date + * format is the date format that is to be used with some applications that + * are HTTP/1.0 compliant. The RFC 1123 date format is + * <pre> + * asctime = 'weekday SP date3 SP time SP 4DIGIT'. + * date3 = 'month SP (2DIGIT | (SP 1DIGIT))' and + * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT'. + * </pre> + */ + private void asctime(){ + wkday(); + off++; + date3(); + off++; + time(); + off++; + year4(); + } + + /** + * This is the date1 format of a date that is used by the RFC 1123 + * date format. This date is + * <pre> + * date1 = '2DIGIT SP month SP 4DIGIT'. + * example '02 Jun 1982'. + * </pre> + */ + private void date1(){ + day(); + off++; + month(); + off++; + year4(); + } + + /** + * This is the date2 format of a date that is used by the RFC 850 + * date format. This date is + * <pre> + * date2 = '2DIGIT "-" month "-" 2DIGIT' + * example '02-Jun-82'. + * </pre> + */ + private void date2(){ + day(); + off++; + month(); + off++; + year2(); + } + + /** + * This is the date3 format of a date that is used by the asctime + * date format. This date is + * <pre> + * date3 = 'month SP (2DIGIT | (SP 1DIGIT))' + * example 'Jun 2'. + * <pre> + */ + private void date3(){ + month(); + off++; + day(); + } + + /** + * This is used to parse a consecutive set of digit characters to create + * the day of the week. This will tolerate a space on front of the digits + * thiswill allow all date formats including asctime to use this to get + * the day. This may parse more than 2 digits, however if there are more + * than 2 digits the date format is incorrect anyway. + */ + private void day(){ + if(space(buf[off])){ + off++; + } + while(off < count){ + if(!digit(buf[off])){ + break; + } + day *= 10; + day += buf[off]; + day -= '0'; + off++; + } + } + + /** + * This is used to get the year from a set of digit characters. This is + * used to parse years that are of the form of 2 digits (e.g 82) however + * this will assume that any dates that are in 2 digit format are dates + * for the 2000 th milleneum so 01 will be 2001. + * <p> + * This may parse more than 2 digits but if there are more than 2 digits + * in a row then the date format is incorrect anyway. + */ + private void year2(){ + int mill = 2000; /* milleneum */ + int cent = 0; /* century */ + while(off < count){ + if(!digit(buf[off])){ + break; + } + cent *= 10; + cent += buf[off]; + cent -= '0'; + off++; + } + year= mill+cent; /* result 4 digits*/ + } + + /** + * This is used to get the year from a set of digit characters. This + * is used to parse years that are of the form of 4 digits (e.g 1982). + * <p> + * This may parse more than 4 digits but if there are more than 2 + * digits in a row then the date format is incorrect anyway. + */ + private void year4() { + while(off < count){ + if(!digit(buf[off])){ + break; + } + year *= 10; + year += buf[off]; + year -= '0'; + off++; + } + } + + /** + * This is used to parse the time for a HTTP-date. The time for a + * HTTP-date is in the form <code>00:00:00</code> that is + * <pre> + * time = '2DIGIT ":" 2DIGIT ":" 2DIGIT' so this will + * read only a time of that form, although this will + * parse time = '2DIGIT CHAR 2DIGIT CHAR 2DIGIT'. + * </pre> + */ + private void time(){ + hours(); + off++; + mins(); + off++; + secs(); + } + + /** + * This is used to initialize the hour. This will read a consecutive + * sequence of digit characters and convert them into a decimal number + * to represent the hour that this date represents. + * <p> + * This may parse more than 2 digits but if there are more than 2 + * digits the date is already incorrect. + */ + private void hours(){ + while(off < count){ + if(!digit(buf[off])){ + break; + } + hour *= 10; + hour += buf[off]; + hour -= '0'; + off++; + } + } + + /** + * This is used to initialize the mins. This will read a consecutive + * sequence of digit characters and convert them into a decimal number + * to represent the mins that this date represents. + * <p> + * This may parse more than 2 digits but if there are more than 2 + * digits the date is already incorrect. + */ + private void mins(){ + while(off < count){ + if(!digit(buf[off])){ + break; + } + mins *= 10; + mins += buf[off]; + mins -= '0'; + off++; + } + } + + /** + * This is used to initialize the secs. This will read a consecutive + * sequence of digit characters and convert them into a decimal + * number to represent the secs that this date represents. + * <p> + * This may parse more than 2 digits but if there are more than 2 + * digits the date is already incorrect + */ + private void secs(){ + while(off < count){ + if(!digit(buf[off])){ + break; + } + secs *= 10; + secs += buf[off]; + secs -= '0'; + off++; + } + } + + /** + * This is used to read the week day of HTTP-date. The shorthand day + * (e.g Mon for Monday) is used by the RFC 1123 and asctime date formats. + * This will simply try to read each day from the buffer, when the day + * is read successfully then the index of that day is saved. + */ + private void wkday(){ + for(int i =0; i < WKDAYS.length;i++){ + if(skip(WKDAYS[i])){ + weekday = i; + return; + } + } + } + + /** + * This is used to read the week day of HTTP-date. This format is used + * by the RFC 850 date format. This will simply try to read each day from + * the buffer, when the day is read successfully then the index of that + * day is saved. + */ + private void weekday(){ + for(int i =0; i < WKDAYS.length;i++){ + if(skip(WEEKDAYS[i])){ + weekday = i; + return; + } + } + } + + /** + * This is used to read the month of HTTP-date. This will simply + * try to read each month from the buffer, when the month is read + * successfully then the index of that month is saved. + */ + private void month(){ + for(int i =0; i < MONTHS.length;i++){ + if(skip(MONTHS[i])){ + month = i; + return; + } + } + } + + /** + * This is used to append the date in RFC 1123 format to the given + * string builder. This will append the date and a trailing space + * character to the buffer. Dates like the following are appended. + * <pre> + * Tue, 02 Jun 1982 + * </pre>. + * For performance reasons a string builder is used. This avoids + * an unneeded synchronization caused by the string buffers. + * + * @param builder this is the builder to append the date to + */ + private void date(StringBuilder builder) { + builder.append(WKDAYS[weekday]); + builder.append(", "); + + if(day <= 9) { + builder.append('0'); + } + builder.append(day); + builder.append(' '); + builder.append(MONTHS[month]); + builder.append(' '); + builder.append(year); + builder.append(' '); + } + + /** + * This is used to append the time in RFC 1123 format to the given + * string builder. This will append the time and a trailing space + * character to the buffer. Times like the following are appended. + * <pre> + * 23:59:59 + * </pre>. + * For performance reasons a string builder is used. This avoids + * an unneeded synchronization caused by the string buffers. + * + * @param builder this is the builder to write the time to + */ + private void time(StringBuilder builder) { + if(hour <= 9) { + builder.append('0'); + } + builder.append(hour); + builder.append(':'); + + if(mins <= 9) { + builder.append('0'); + } + builder.append(mins); + builder.append(':'); + + if(secs <= 9) { + builder.append('0'); + } + builder.append(secs); + builder.append(' '); + } + + /** + * This is used to append the time zone to the provided appender. + * For HTTP the dates should always be in GMT format. So this will + * simply append the "GMT" string to the end of the builder. + * + * @param builder this builder to append the time zone to + */ + private void zone(StringBuilder builder) { + builder.append("GMT"); + } + + /** + * This returns the date in as a <code>long</code>, given the exact + * time this will use the <code>java.util.Date</code> to parse this date + * into a <code>long</code>. The <code>GregorianCalendar</code> uses + * the method <code>getTime</code> which produces the <code>Date</code> + * object from this the <code>getTime</code> returns the <code>long</code> + * + * @return the date parsed as a <code>long</code> + */ + public long toLong() { + Calendar calendar = Calendar.getInstance(ZONE); /* GMT*/ + calendar.set(year,month, day, hour, mins, secs); + calendar.set(MILLISECOND, 0); + + return calendar.getTime().getTime(); + } + + /** + * This prints the date in the format of a RFC 1123 date. Example + * <pre> + * Tue, 02 Jun 1982 23:59:59 GMT + * </pre>. + * This uses a <code>StringBuffer</code> to accumulate the various + * <code>String</code>s/<code>int</code>s to form the resulting date + * value. The resulting date value is the one required by RFC 2616. + * <p> + * The HTTP date must be in the form of RFC 1123. The hours, minutes + * and seconds are appended with the 0 character if they are less than + * 9 i.e. if they do not have two digits. + * + * @return the date in RFC 1123 format + */ + public String toString(){ + StringBuilder builder = new StringBuilder(30); + + date(builder); + time(builder); + zone(builder); + + return builder.toString(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/LanguageParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/LanguageParser.java new file mode 100644 index 0000000..0a2d215 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/LanguageParser.java @@ -0,0 +1,156 @@ +/* + * LanguageParser.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import java.util.List; +import java.util.Locale; + +/** + * LanguageParser is used to parse the HTTP <code>Accept-Language</code> + * header. This takes in an <code>Accept-Language</code> header and parses + * it according the RFC 2616 BNF for the <code>Accept-Language</code> header. + * This also has the ability to sequence the language tokens in terms of + * the most preferred and the least preferred. + * <p> + * This uses the qvalues outlined by RFC 2616 to order the language tokens + * by preference. Typically the language tokens will not have qvalues with + * the language. However when a language tag has the qvalue parameter then + * this tag will be ordered based on the value of that parameter. A language + * tag without the qvalue parameter is considered to have a qvalue of 1 and + * is ordered accordingly. + * + * @author Niall Gallagher + */ +public class LanguageParser extends ListParser<Locale> { + + /** + * This is used to create a <code>LanguageParser</code> for the + * <code>Accept-Language</code> HTTP header value. This will + * parse a set of language tokens and there parameters. The + * languages will be ordered on preference. This constructor + * will parse the value given using <code>parse(String)</code>. + */ + public LanguageParser() { + super(); + } + + /** + * This is used to create a <code>LanguageParser</code> for the + * <code>Accept-Language</code> HTTP header value. This will + * parse a set of language tokens and there parameters. The + * languages will be ordered on preference. This constructor + * will parse the value given using <code>parse(String)</code>. + * + * @param text value of a <code>Accept-Language</code> header + */ + public LanguageParser(String text) { + super(text); + } + + /** + * This is used to create a <code>LanguageParser</code> for the + * <code>Accept-Language</code> HTTP header value. This will + * parse a set of language tokens and there parameters. The + * languages will be ordered on preference. This constructor + * will parse the value given using <code>parse(String)</code>. + * + * @param list value of a <code>Accept-Language</code> header + */ + public LanguageParser(List<String> list) { + super(list); + } + + /** + * This creates a locale object using an offset and a length. + * The locale is created from the extracted token and the offset + * and length ensure that no leading or trailing whitespace are + * within the created locale object. + * + * @param text this is the text buffer to acquire the value from + * @param start the offset within the array to take characters + * @param len this is the number of characters within the token + */ + @Override + protected Locale create(char[] text, int start, int len){ + String language = language(text, start, len); + String country = country(text, start, len); + + return new Locale(language, country); + } + + /** + * This will extract the primary language tag from the header. + * This token is used to represent the language that will be + * available in the <code>Locale</code> object created. + * + * @param text this is the text buffer to acquire the value from + * @param start the offset within the array to take characters + * @param len this is the number of characters within the token + */ + private String language(char[] text, int start, int len) { + int mark = start; + int size = 0; + + while(start < len) { + char next = text[start]; + + if(terminal(next)) { + return new String(text, mark, size); + } + size++; + start++; + } + return new String(text, mark, len); + } + + /** + * This will extract the primary country tag from the header. + * This token is used to represent the country that will be + * available in the <code>Locale</code> object created. + * + * @param text this is the text buffer to acquire the value from + * @param start the offset within the array to take characters + * @param len this is the number of characters within the token + */ + private String country(char[] text, int start, int len) { + int size = len; + + while(start < len) { + if(text[start++] == '-') { + return new String(text, start, --size); + } + size--; + } + return ""; + } + + /** + * This is used to determine whether the character provided is + * a terminal character. The terminal token is the value that is + * used to separate the country from the language and also any + * character the marks the end of the language token. + * + * @param ch this is the character that is to be evaluated + * + * @return true if the character represents a terminal token + */ + private boolean terminal(char ch) { + return ch ==' ' || ch == '-' || ch == ';'; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/ListParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/ListParser.java new file mode 100644 index 0000000..166a2aa --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/ListParser.java @@ -0,0 +1,456 @@ +/* + * ListParser.java September 2003 + * + * Copyright (C) 2003, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import static java.lang.Long.MAX_VALUE; + +import java.util.ArrayList; +import java.util.List; +import java.util.PriorityQueue; + +import org.simpleframework.common.parse.Parser; + +/** + * The <code>ListParser</code> is used to extract a comma separated + * list of HTTP header values. This will extract values without + * any leading or trailing spaces, which enables the values to be + * used. Listing the values that appear in the header also requires + * that the values are ordered. This orders the values using the + * values that appear with any quality parameter associated with it. + * The quality value is a special parameter that often found in a + * comma separated value list to specify the client preference. + * <pre> + * + * image/gif, image/jpeg, text/html + * image/gif;q=1.0, image/jpeg;q=0.8, image/png; q=1.0,*;q=0.1 + * gzip;q=1.0, identity; q=0.5, *;q=0 + * + * </pre> + * The above lists taken from RFC 2616 provides an example of the + * common form comma separated values take. The first illustrates + * a simple comma delimited list, here the ordering of values is + * determined from left to right. The second and third list have + * quality values associated with them, these are used to specify + * a preference and thus order. + * <p> + * Each value within a list has an implicit quality value of 1.0. + * If the value is explicitly set with a the "q" parameter, then + * the values can range from 1.0 to 0.001. This parser ensures + * that the order of values returned from the <code>list</code> + * method adheres to the optional quality parameters and ensures + * that the quality parameters a removed from the resulting text. + * + * @author Niall Gallagher + */ +public abstract class ListParser<T> extends Parser { + + /** + * Provides a quick means of sorting the values extracted. + */ + private PriorityQueue<Entry> order; + + /** + * Contains all the values extracted from the header(s). + */ + private List<T> list; + + /** + * This is used as a working space to parse the value. + */ + private char[] text; + + /** + * The quality associated with an individual value. + */ + private long qvalue; + + /** + * Used to index into the write offset for the value. + */ + private int pos; + + /** + * This is used to determine whether to gather tokens. + */ + private boolean build; + + /** + * Constructor for the <code>ListParser</code>. This creates a + * parser with no initial parse data, if there are headers to + * be parsed then the <code>parse(String)</code> method or + * <code>parse(List)</code> method can be used. This will + * parse a delimited list according so RFC 2616 section 4.2. + */ + public ListParser(){ + this.order = new PriorityQueue<Entry>(); + this.list = new ArrayList<T>(); + this.text = new char[0]; + } + + /** + * Constructor for the <code>ListParser</code>. This creates a + * parser with the text supplied. This will parse the comma + * separated list according to RFC 2616 section 2.1 and 4.2. + * The tokens can be extracted using the <code>list</code> + * method, which will also sort and trim the tokens. + * + * @param text this is the comma separated list to be parsed + */ + public ListParser(String text) { + this(); + parse(text); + } + + /** + * Constructor for the <code>ListParser</code>. This creates a + * parser with the text supplied. This will parse the comma + * separated list according to RFC 2616 section 2.1 and 4.2. + * The tokens can be extracted using the <code>list</code> + * method, which will also sort and trim the tokens. + * + * @param list a list of comma separated lists to be parsed + */ + public ListParser(List<String> list) { + this(); + parse(list); + } + + /** + * This allows multiple header values to be represented as one + * single comma separated list. RFC 2616 states that multiple + * message header fields with the same field name may be present + * in a message if and only if the entire field value for that + * header field is defined as a comma separated list. This means + * that if there are multiple header values with the same name + * they can be combined into a single comma separated list. + * + * @param list this is a list of header values to be combined + */ + public void parse(List<String> list) { + for(String value : list) { + parse(value); + build = true; + } + build = false; + } + + /** + * This will build an ordered list of values extracted from the + * comma separated header value. This enables the most preferred + * token, to be taken from the first index of the array and the + * least preferred token to be taken from the last index. + * + * @return tokens parsed from the list ordered by preference + */ + public List<T> list() { + return list; + } + + /** + * This is used to remove the <code>String</code> tokens from + * the priority queue and place those tokens in an array. The + * The <code>String</code> tokens are placed into the array + * in an ordered manner so that the most preferred token is + * inserted into the start of the list. + */ + private void build() { + while(!order.isEmpty()) { + Entry entry = order.remove(); + T value = entry.getValue(); + + list.add(value); + } + } + + /** + * This ensures that tokens are taken from the comma separated + * list as long as there bytes left to be examined within the + * source text. This also makes sure that the implicit qvalue + * is decreased each time a token is extracted from the list. + */ + protected void parse() { + while(off < count) { + clear(); + value(); + save(); + } + build(); + } + + /** + * Initializes the parser so that tokens can be extracted from + * the list. This creates a write buffer so that a if there is + * only one token as long as the source text, then that token + * can be accommodated, also this starts of the initial qvalue + * implicit to tokens within the list as the maximum long value. + * <p> + * One thing that should be noted is that this will not empty + * the priority queue on each string parsed. This ensures that + * if there are multiple strings they can be parsed quickly + * and also contribute to the final result. + */ + protected void init(){ + if(text.length < count){ + text = new char[count]; + } + if(!build) { + list.clear(); + } + pos = off = 0; + order.clear(); + } + + /** + * This is used to return the parser to a semi-initialized state. + * After extracting a token from the list the buffer will have + * accumulated bytes, this ensures that bytes previously written + * to the buffer do not interfere with the next token extracted. + * <p> + * This also ensures the implicit qvalue is reset to the maximum + * long value, so that the next token parsed without a qvalue + * will have the highest priority and be placed at the top of + * the list. This ensures order is always maintained. + */ + private void clear() { + qvalue = MAX_VALUE; + pos = 0; + } + + /** + * This method will extract a token from a comma separated list + * and write it to a buffer. This performs the extraction in such + * a way that it can tolerate literals, parameters, and quality + * value parameters. The only alterations made to the token by + * this method is the removal of quality values, that is, qvalue + * parameters which have the name "q". Below is an example of + * some of the lists that this can parse. + * <pre> + * + * token; quantity=1;q=0.001, token; text="a, b, c, d";q=0 + * image/gif, , image/jpeg, image/png;q=0.8, * + * token="\"a, b, c, d\", a, b, c, d", token="a";q=0.9,, + * + * </pre> + * This will only interpret a comma delimiter outside quotes of + * a literal. So if there are comma separated tokens that have + * quoted strings, then commas within those quoted strings will + * not upset the extraction of the token. Also escaped strings + * are tolerated according to RFC 2616 section 2. + */ + private void value() { + parse: while(off < count) { + if(buf[off++] == '"'){ /* "[t]ext" */ + text[pos++] = buf[off-1]; /* ["]text"*/ + while(++off < count){ /* "text"[] */ + if(buf[off -1] =='"'){ /* "text["] */ + if(buf[off -2] !='\\') + break; + } + text[pos++] = buf[off-1]; /* "tex[t]"*/ + } + } else if(buf[off -1] == ';'){ /* [;] q=0.1 */ + for(int seek = off; seek+1 < count;){/* ;[ ]q=0.1 */ + if(!space(buf[seek])){ /* ;[ ]q=0.1*/ + if(buf[seek] =='q'){ /* ; [q]=0.1*/ + if(buf[seek+1] =='='){ /* ; q[=]0.1*/ + off = seek; + qvalue(); + continue parse; + } + } + break; + } + seek++; + } + } + if(buf[off-1] ==','){ + break; + } + text[pos++] = buf[off-1]; + } + } + + /** + * This method will trim whitespace from the extracted token and + * store that token within the <code>PriorityQueue</code>. This + * ensures that the tokens parsed from the comma separated list + * can be used. Trimming the whitespace is something that will be + * done to the tokens so that they can be examined, so this + * ensures that the overhead of the <code>String.trim</code> + * method is not required to remove trailing or leading spaces. + * This also ensures that empty tokens are not saved. + */ + private void save() { + int size = pos; + int start = 0; + + while(size > 0){ + if(!space(text[size-1])){ + break; + } + size--; + } + while(start < pos){ + if(space(text[start])){ + start++; + size--; + }else { + break; + } + } + if(size > 0) { + T value = create(text, start, size); + + if(value != null) { + save(value); + } + } + } + + /** + * This stores the string in the <code>PriorityQueue</code>. If + * the qvalue extracted from the header value is less that 0.001 + * then this will not store the token. This ensures that client + * applications can specify tokens that are unacceptable to it. + * + * @param value this is the token to be enqueued into the queue + */ + private void save(T value) { + int size = order.size(); + + if(qvalue > 0) { + order.offer(new Entry(value, qvalue, size)); + } + } + + /** + * This is used to extract the qvalue parameter from the header. + * The qvalue parameter is identified by a parameter with the + * name "q" and a numeric floating point number. The number can + * be in the range of 0.000 to 1.000. The <code>qvalue</code> + * is parsed byte bit shifting a byte in to a value in to a + * long, this may cause problems with varying accuracy. + */ + private void qvalue() { + if(skip("q=")){ + char digit = 0; + + for(qvalue = 0; off < count;){ + if(buf[off] == '.'){ + off++; + continue; + } + if(!digit(buf[off])){ + break; + } + digit = buf[off]; + digit -= '0'; + qvalue |= digit; + qvalue <<= 4; + off++; + } + } + } + + /** + * This creates an value object using the range of characters + * that have been parsed as an item within the list of values. It + * is up to the implementation to create a value to insert in to + * the list. A null value will be ignored if returned. + * + * @param text this is the text buffer to acquire the value from + * @param start the offset within the array to take characters + * @param len this is the number of characters within the token + */ + protected abstract T create(char[] text, int start, int len); + + /** + * The <code>Entry</code> object provides a comparable object to + * insert in to a priority queue. This will sort the value using + * the quality value parameter parsed from the list. If there + * are values with the same quality value this this will sort + * the values by a secondary order parameter. + */ + private class Entry implements Comparable<Entry> { + + /** + * This is the value that is represented by this entry. + */ + private final T value; + + /** + * This is the priority value that is used to sort entries. + */ + private final long priority; + + /** + * This is the secondary order value used to sort entries. + */ + private final int order; + + /** + * Constructor for the <code>Entry</code> object. This is used + * to create a comparable value that can be inserted in to a + * priority queue and extracted in order of the priority value. + * + * @param value this is the value that is represented by this + * @param priority this is the priority value for sorting + * @param order this is the secondary priority value used + */ + public Entry(T value, long priority, int order) { + this.priority = priority; + this.order = order; + this.value = value; + } + + /** + * This acquires the value represented by this entry. This is + * can be used to place the value within a list as it is taken + * from the priority queue. Acquiring the values in this way + * facilitates a priority ordered list of values. + * + * @return this returns the value represented by this + */ + public T getValue() { + return value; + } + + /** + * This is used to sort the entries within the priority queue + * using the provided priority of specified. If the entries + * have the same priority value then they are sorted using a + * secondary order value, which is the insertion index. + * + * @param entry this is the entry to be compared to + * + * @return this returns the result of the entry comparison + */ + public int compareTo(Entry entry) { + long value = entry.priority - priority; + + if(value > 0) { + return 1; + } + if(value < 0) { + return -1; + } + return order - entry.order; + } + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/PathParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/PathParser.java new file mode 100644 index 0000000..7055e4e --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/PathParser.java @@ -0,0 +1,726 @@ +/* + * PathParser.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import org.simpleframework.common.parse.Parser; +import org.simpleframework.http.Path; + +/** + * This is used to parse a path given as part of a URI. This will read the + * path, normalize it, and break it up into its components. The normalization + * of the path is the conversion of the path given into it's actual path by + * removing the references to the parent directories and to the current dir. + * <p> + * If the path that this represents is <code>/usr/bin/../etc/./README</code> + * then the actual path, normalized, is <code>/usr/etc/README</code>. Once + * the path has been normalized it is possible to acquire the segments as + * an array of strings, which allows simple manipulation of the path. + * <p> + * Although RFC 2396 defines the path within a URI to have parameters this + * does not extract those parameters this will simply normalize the path and + * include the path parameters in the path. If the path is to be converted + * into a OS specific file system path that has the parameters extracted + * then the <code>AddressParser</code> should be used. + * + * @author Niall Gallagher + */ +public class PathParser extends Parser implements Path{ + + /** + * Used to store the individual path segments. + */ + private TokenList list; + + /** + * Used to store consumed name characters. + */ + private Token name; + + /** + * Used to store consumed file extension. + */ + private Token ext; + + /** + * Used to store the highest directory path. + */ + private Token dir; + + /** + * Used to store consumed normalized path name. + */ + private Token path; + + /** + * The default constructor will create a <code>PathParser</code> that + * contains no specifics. The instance will return <code>null</code> + * for all the get methods. The <code>PathParser</code>'s get methods + * may be populated by using the parse method. + */ + public PathParser() { + this.list = new TokenList(); + this.ext = new Token(); + this.dir = new Token(); + this.path = new Token(); + this.name = new Token(); + } + + /** + * This is primarily a convineance constructor. This will parse the + * <code>String</code> given to extract the specifics. This could be + * achived by calling the default no-arg constructor and then using + * the instance to invoke the <code>parse</code> method on that + * <code>String</code> to extract the parts. + * + * @param path a <code>String</code> containing a path value + */ + public PathParser(String path){ + this(); + parse(path); + } + + /** + * This will parse the path in such a way that it ensures that at no + * stage there are trailing back references, using path normalization. + * The need to remove the back references is so that this + * <code>PathParser</code> will create the same <code>String</code> + * path given a set of paths that have different back references. For + * example the paths <code>/path/../path</code> and <code>/path</code> + * are the same path but different <code>String</code>'s. + * <p> + * This will NOT parse an immediate back reference as this signifies + * a path that cannot exist. So a path such as <code>/../</code> will + * result in a null for all methods. Paths such as <code>../bin</code> + * will not be allowed. + */ + protected void parse() { + normalize(); + path(); + segments(); + name(); + extension(); + } + + /** + * This will initialize the parser so that it is in a ready state. + * This allows the parser to be used to parse many paths. This will + * clear the parse buffer objects and reset the offset to point to + * the start of the char buffer. The count variable is reset by the + * <code>Parser.parse</code> method. + */ + protected void init() { + list.clear(); + ext.clear(); + dir.clear(); + name.clear(); + path.clear(); + off = 0; + } + + /** + * This will return the extension that the file name contains. + * For example a file name <code>file.en_US.extension</code> + * will produce an extension of <code>extension</code>. This + * will return null if the path contains no file extension. + * + * @return this will return the extension this path contains + */ + public String getExtension() { + return ext.toString(); + } + + /** + * This will return the full name of the file without the path. + * As regargs the definition of the path in RFC 2396 the name + * would be considered the last path segment. So if the path + * was <code>/usr/README</code> the name is <code>README</code>. + * Also for directorys the name of the directory in the last + * path segment is returned. This returns the name without any + * of the path parameters. As RFC 2396 defines the path to have + * path parameters after the path segments. + * + * @return this will return the name of the file in the path + */ + public String getName(){ + return name.toString(); + } + + /** + * This will return the normalized path. The normalized path is + * the path without any references to its parent or itself. So + * if the path to be parsed is <code>/usr/../etc/./</code> the + * path is <code>/etc/</code>. If the path that this represents + * is a path with an immediate back reference then this will + * return null. This is the path with all its information even + * the parameter information if it was defined in the path. + * + * @return this returns the normalize path without + * <code>../</code> or <code>./</code> + */ + public String getPath() { + return path.toString(); + } + + /** + * This will return the normalized path from the specified path + * segment. This allows various path parts to be acquired in an + * efficient means what does not require copy operations of the + * use of <code>substring</code> invocations. Of particular + * interest is the extraction of context based paths. This is + * the path with all its information even the parameter + * information if it was defined in the path. + * + * @param from this is the segment offset to get the path for + * + * @return this returns the normalize path without + * <code>../</code> or <code>./</code> + */ + public String getPath(int from) { + return list.segment(from); + } + + /** + * This will return the normalized path from the specified path + * segment. This allows various path parts to be acquired in an + * efficient means what does not require copy operations of the + * use of <code>substring</code> invocations. Of particular + * interest is the extraction of context based paths. This is + * the path with all its information even the parameter + * information if it was defined in the path. + * + * @param from this is the segment offset to get the path for + * @param count this is the number of path segments to include + * + * @return this returns the normalize path without + * <code>../</code> or <code>./</code> + */ + public String getPath(int from, int count) { + return list.segment(from, count); + } + + /** + * This will return the highest directory that exists within + * the path. This is used to that files within the same path + * can be acquired. An example of that this would do given + * the path <code>/pub/./bin/README</code> would be to return + * the highest directory path <code>/pub/bin/</code>. The "/" + * character will allways be the last character in the path. + * + * @return this method will return the highest directory + */ + public String getDirectory(){ + return dir.toString(); + } + + /** + * This method is used to break the path into individual parts + * called segments, see RFC 2396. This can be used as an easy + * way to compare paths and to examine the directory tree that + * the path points to. For example, if an path was broken from + * the string <code>/usr/bin/../etc</code> then the segments + * returned would be <code>usr</code> and <code>etc</code> as + * the path is normalized before the segments are extracted. + * + * @return return all the path segments within the directory + */ + public String[] getSegments(){ + return list.list(); + } + + /** + * This will return the path as it is relative to the issued + * path. This in effect will chop the start of this path if + * it's start matches the highest directory of the given path + * as of <code>getDirectory</code>. This is useful if paths + * that are relative to a specific location are required. To + * illustrate what this method will do the following example + * is provided. If this object represented the path string + * <code>/usr/share/rfc/rfc2396.txt</code> and the issued + * path was <code>/usr/share/text.txt</code> then this will + * return the path string <code>/rfc/rfc2396.txt</code>. + * + * @param path the path prefix to acquire a relative path + * + * @return returns a path relative to the one it is given + * otherwize this method will return null + */ + public String getRelative(String path){ + return getRelative(new PathParser(path)); + } + + /** + * This is used by the <code>getRelative(String)</code> to + * normalize the path string and determine if it contains a + * highest directory which is shared with the path that is + * represented by this object. If the path has leading back + * references, such as <code>../</code>, then the result of + * this is null. The returned path begins with a '/'. + * + * @param path the path prefix to acquire a relative path + * + * @return returns a path relative to the one it is given + * otherwize this method will return null + */ + private String getRelative(PathParser path){ + char[] text = path.buf; + int off = path.dir.off; + int len = path.dir.len; + + return getRelative(text, off, len); + } + + /** + * This will return the path as it is relative to the issued + * path. This in effect will chop the start of this path if + * it's start matches the highest directory of the given path + * as of <code>getDirectory</code>. This is useful if paths + * that are relative to a specific location are required. To + * illustrate what this method will do the following example + * is provided. If this object represented the path string + * <code>/usr/share/rfc/rfc2396.txt</code> and the issued + * path was <code>/usr/share/text.txt</code> then this will + * return the path string <code>/rfc/rfc2396.txt</code>. + * + * @param text the path prefix to acquire a relative path + * @param off this is the offset within the text to read + * @param len this is the number of characters in the path + * + * @return returns a path relative to the one it is given + * otherwize this method will return null + */ + private String getRelative(char[] text, int off, int len){ + if (len > path.len) { + return null; + } + int size = path.len - len + 1; /* '/' */ + int pos = path.off + len - 1; + + for(int i = 0; i < len; i++){ + if(text[off++] != buf[path.off+i]){ + return null; + } + } + if(pos < 0) { /* ../ */ + return null; + } + return new String(buf,pos,size); + } + + /** + * This will extract the path of the given <code>String</code> + * after it has been normalized. If the path can not be normalized + * then the count is set to -1 and the path cannot be extracted. + * When this happens then the path parameter is <code>null</code>. + */ + private void path() { + if(count > 0){ + path.len = count; + path.off = 0; + } + } + + /** + * This will simply read the characters from the end of the + * buffer until it encounters the first peroid character. When + * this is read it will store the file extension and remove the + * characters from the buffer. + */ + private void extension() { + int pos = off + count; /* index.html[]*/ + int len = 0; + + while(pos-1 >= off) { /* index.htm[l]*/ + if(buf[--pos]=='.'){ /* index[.]html*/ + ext.off = pos+1; + ext.len = len; + count = pos; + break; + } + len++; + } + } + + /** + * This wil extract each individual segment from the path and + * also extract the highest directory. The path segments are + * basically the strings delimited by the '/' character of a + * normalized path. As well as extracting the path segments + * this will also extract the directory of path, that is, the + * the path up to the last occurance of the '/' character. + */ + private void segments() { + int pos = count - 1; + int len = 1; + + if(count > 0){ + if(buf[pos] == '/'){ /* /pub/bin[/] */ + dir.len = pos+1; + dir.off = 0; + pos--; /* /pub/bi[n]/ */ + } + while(pos >= off){ + if(buf[pos] == '/'){ /* /pub[/]bin/*/ + if(dir.len == 0){ + dir.len = pos+1; /* [/] is 0*/ + dir.off = 0; + } + list.add(pos+1,len-1); + len = 0; + } + len++; + pos--; + } + } + } + + /** + * The normalization of the path is the conversion of the path + * given into it's actual path by removing the references to + * the parent directorys and to the current dir. So if the path + * given was <code>/usr/bin/../etc/./README</code> then the actual + * path, the normalized path, is <code>/usr/etc/README</code>. + * <p> + * This method ensures the if there are an illegal number of back + * references that the path will be evaluated as empty. This can + * evaluate any path configuration, this includes any references + * like <code>../</code> or <code>/..</code> within the path. + */ + private void normalize(){ + int size = count + off; + int pos = off; + + for(off = count = 0; pos < size; pos++) { + buf[count++] = buf[pos]; + + if(buf[pos] == '.') { /* //[.]/path/ */ + if(count -1 > 0) { /* /[/]./path/ */ + if(buf[count - 2] !='/') /* /[/]./path./ */ + continue; /* /path.[/] */ + } + if(pos + 2 > size){ /* /path/[.] */ + count--; + } else { + if(buf[pos + 1] =='/'){ /* /.[/]path */ + pos++;/* /[/]. */ + count--; /* /.[/]path */ + } + if(buf[pos] !='.'){ /* /.[/]path */ + continue; + } + if(pos + 2< size){ + if(buf[pos + 2]!='/') /* /..[p]ath */ + continue; /* /[.].path */ + } + if(count - 2 > 0) { + for(count -= 2; count - 1 > 0;){ /* /path[/]..*/ + if(buf[count - 1]=='/') { /* [/]path/..*/ + break; + } + count--; + } + }else { /* /../ */ + count = 0; + off = 0; + break; + } + pos += 2; /* /path/.[.]/ */ + } + } + } + } + + /** + * This will extract the full name of the file without the path. + * As regards the definition of the path in RFC 2396 the name + * would be considered the last path segment. So if the path + * was <code>/usr/README</code> the name is <code>README</code>. + * Also for directorys the name of the directory in the last + * path segment is returned. This returns the name without any + * of the path parameters. As RFC 2396 defines the path to have + * path parameters after the path segments. So the path for the + * directory "/usr/bin;param=value/;param=value" would result + * in the name "bin". If the path given was "/" then there will + * be nothing in the buffer because <code>extract</code> will + * have removed it. + */ + private void name(){ + int pos = count; + int len = 0; + + while(pos-- > off) { /* /usr/bin/;para[m] */ + if(buf[pos]==';'){ /* /usr/bin/[;]param */ + if(buf[pos-1]=='/'){ /* /usr/bin[/];param */ + pos--; /* /usr/bin[/];param */ + } + len = 0; /* /usr/bin[/]*/ + }else if(buf[pos]=='/'){ /* /usr[/]bin*/ + off = pos + 1; /* /usr/[b]in*/ + count = len; /* [b]in */ + break; + }else{ + len++; + } + } + name.len = count; + name.off = off; + } + + /** + * This will return the normalized path. The normalized path is + * the path without any references to its parent or itself. So + * if the path to be parsed is <code>/usr/../etc/./</code> the + * path is <code>/etc/</code>. If the path that this represents + * is a path with an immediate back reference then this will + * return null. This is the path with all its information even + * the parameter information if it was defined in the path. + * + * @return this returns the normalize path without + * <code>../</code> or <code>./</code> + */ + public String toString(){ + return getPath(); + } + + /** + * This is used so that the <code>PathParser</code> can speed + * up the parsing of the data. Rather than using a buffer like + * a <code>ParseBuffer</code> or worse a <code>StringBuffer</code> + * this just keeps an index into the character array from the + * start and end of the token. Also this enables a cache to be + * kept so that a <code>String</code> does not need to be made + * again after the first time it is created. + */ + private class Token { + + /** + * Provides a quick retrieval of the token value. + */ + public String value; + + /** + * Offset within the buffer that the token starts. + */ + public int off; + + /** + * Length of the region that the token consumes. + */ + public int len; + + /** + * If the <code>Token</code> is to be reused this will clear + * all previous data. Clearing the buffer allows it to be + * reused if there is a new URI to be parsed. This ensures + * that a null is returned if the token length is zero. + */ + public void clear() { + value = null; + len = 0; + } + + /** + * This method will convert the <code>Token</code> into it's + * <code>String</code> equivelant. This will firstly check + * to see if there is a value, for the string representation, + * if there is the value is returned, otherwise the region + * is converted into a <code>String</code> and returned. + * + * @return this returns a value representing the token + */ + public String toString() { + if(value != null) { + return value; + } + if(len > 0) { + value = new String(buf,off,len); + } + return value; + } + } + + /** + * The <code>TokenList</code> class is used to store a list of + * tokens. This provides an <code>add</code> method which can + * be used to store an offset and length of a token within + * the buffer. Once the tokens have been added to they can be + * examined, in the order they were added, using the provided + * <code>list</code> method. This has a scalable capacity. + */ + private class TokenList { + + /** + * This is used to cache the segments that are created. + */ + private String[] cache; + + /** + * Contains the offsets and lengths of the tokens. + */ + private int[] list; + + /** + * Determines the write offset into the array. + */ + private int count; + + /** + * Constructor for the <code>TokenList</code> is used to + * create a scalable list to store tokens. The initial + * list is created with an array of sixteen ints, which + * is enough to store eight tokens. + */ + private TokenList(){ + list = new int[16]; + } + + /** + * This is used to acquire the path from the segment that + * is specified. This provides an efficient means to get + * the path without having to perform expensive copy of + * substring operations. + * + * @param from this is the path segment to get the path + * + * @return the string that is the path segment created + */ + public String segment(int from) { + int total = count / 2; + int left = total - from; + + return segment(from, left); + } + + /** + * This is used to acquire the path from the segment that + * is specified. This provides an efficient means to get + * the path without having to perform expensive copy of + * substring operations. + * + * @param from this is the path segment to get the path + * @param total this is the number of segments to use + * + * @return the string that is the path segment created + */ + public String segment(int from, int total) { + int last = list[0] + list[1] + 1; + + if(from + total < count / 2) { + last = offset(from + total); + } + int start = offset(from); + int length = last - start; + + return new String(buf, start-1, length); + } + + /** + * This is used to acquire the offset within the buffer + * of the specified segment. This allows a path to be + * created that is constructed from a given segment. + * + * @param segment this is the segment offset to use + * + * @return this returns the offset start for the segment + */ + private int offset(int segment) { + int last = count - 2; + int shift = segment * 2; + int index = last - shift; + + return list[index]; + } + + /** + * This is used to add a new token to the list. Tokens + * will be available from the <code>list</code> method in + * the order it was added, so the first to be added will + * at index zero and the last with be in the last index. + * + * @param off this is the read offset within the buffer + * @param len the number of characters within the token + */ + public void add(int off, int len){ + if(count+1 > list.length) { + resize(count *2); + } + list[count++] = off; + list[count++] = len; + } + + /** + * This is used to retrieve the list of tokens inserted + * to this list using the <code>add</code> method. The + * indexes of the tokens represents the order that the + * tokens were added to the list. + * + * @return returns an ordered list of token strings + */ + public String[] list(){ + if(cache == null) { + cache = build(); + } + return cache; + } + + /** + * This is used to retrieve the list of tokens inserted + * to this list using the <code>add</code> method. The + * indexes of the tokens represents the order that the + * tokens were added to the list. + * + * @return returns an ordered list of token strings + */ + private String[] build(){ + String[] value = new String[count/2]; + + for(int i =0, j = count/2; i< count; i+=2){ + int index = j - (i/2) - 1; + int off = list[i]; + int size = list[i + 1]; + + value[index] = new String(buf, off, size); + } + return value; + } + + /** + * This is used to clear all tokens previously stored + * in the list. This is required so that initialization + * of the parser with the <code>init</code> method can + * ensure that there are no tokens from previous data. + */ + public void clear(){ + cache =null; + count =0; + } + + /** + * Scales the internal array used should the number of + * tokens exceed the initial capacity. This will just + * copy across the ints used to represent the token. + * + * @param size length the capacity is to increase to + */ + private void resize(int size){ + int[] copy = new int[size]; + System.arraycopy(list,0,copy,0,count); + list = copy; + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/PrincipalParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/PrincipalParser.java new file mode 100644 index 0000000..52aeff8 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/PrincipalParser.java @@ -0,0 +1,362 @@ +/* + * PrincipalParser.java February 2001 + * + * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import org.simpleframework.common.parse.ParseBuffer; +import org.simpleframework.common.parse.Parser; +import org.simpleframework.http.Principal; + +/** + * PrincipalParser is a parser class for the HTTP basic authorization + * header. It decodes the <code>base64</code> encoding of the user and + * password pair. + * <p> + * This follows the parsing tree of RFC 2617. The goal of this parser + * is to decode the <code>base64</code> encoding of the user name and + * password. After the string has been decoded then the user name and + * password are extracted. This will only parse headers that are from + * the <code>Basic</code> authorization scheme. The format of the basic + * scheme can be found in RFC 2617 and is of the form + * <pre> + * Basic SP base64-encoding. + * </pre> + * + * @author Niall Gallagher + */ +public class PrincipalParser extends Parser implements Principal { + + /** + * Keeps the characters consumed for the password token. + */ + private ParseBuffer password; + + /** + * Keeps the characters consumed for the user name token. + */ + private ParseBuffer user; + + /** + * Keeps the <code>bytes</code> used for decoding base64. + */ + private byte[] four; + + /** + * Tracks the write offset for the buffer. + */ + private int write; + + /** + * Tracks the ready offset for the four buffer. + */ + private int ready; + + /** + * Tracks the read offset for the buffer. + */ + private int read; + + /** + * Creates a <code>Parser</code> for the basic authorization + * scheme. This allows headers that are of this scheme to be + * broken into its component parts i.e. user name and password. + */ + public PrincipalParser() { + this.password = new ParseBuffer(); + this.user = new ParseBuffer(); + this.four = new byte[4]; + } + + /** + * Creates a <code>Parser</code> for the basic authorization + * scheme. This allows headers that are of this scheme to be + * broken into its component parts i.e. user name and password. + * This constructor will parse the <code>String</code> given as + * the header. + * + * @param header this is a header value from the basic scheme + */ + public PrincipalParser(String header){ + this(); + parse(header); + } + + /** + * Gets the users password parsed from the Authorization + * header value. If there was not password parsed from the + * base64 value of the header this returns <code>null</code> + * + * @return the password for the user or <code>null</code> + */ + public String getPassword(){ + if(password.length() == 0){ + return null; + } + return password.toString(); + } + + /** + * Gets the users name from the Authorization header value. + * This will return <code>null</code> if there is no user + * name extracted from the base64 header value. + * + * @return this returns the name of the user + */ + public String getName(){ + if(user.length() == 0){ + return null; + } + return user.toString(); + } + + /** + * Used to parse the actual header data. This will attempt to + * read the "Basic" token from the set of characters given, if + * this is successful then the username and password is + * extracted. + */ + protected void parse(){ + if(skip("Basic ")){ + decode(); + userpass(); + } + } + + /** + * This will initialize the <code>Parser</code> when it is ready + * to parse a new <code>String</code>. This will reset the + * <code>Parser</code> to a ready state. The <code>init</code> method + * is invoked by the <code>Parser</code> when the <code>parse</code> + * method is invoked. + */ + protected void init() { + password.clear(); + user.clear(); + write = ready = + read = off = 0; + pack(); + } + + /** + * This is used to remove all whitespace characters from the + * <code>String</code> excluding the whitespace within literals. + * The definition of a literal can be found in RFC 2616. + * <p> + * The definition of a literal for RFC 2616 is anything between 2 + * quotes but excuding quotes that are prefixed with the backward + * slash character. + */ + private void pack() { + int len = count; + int seek = 0; /* read */ + int pos = 0; /* write */ + char ch = 0; + + while(seek <len){ /* trim start*/ + if(!space(buf[seek])){ + break; + } + seek++; + } + while(seek < len){ + ch = buf[seek++]; + if(space(ch)){ + while(seek < len){ /* skip spaces */ + if(!space(buf[seek])){ + break; + } + seek++; + } + } + buf[pos++] = ch; + } + if(space(ch)){ /* trim end */ + pos--; + } + count = pos; + } + + /** + * Extracts the name and password of the user from the + * <code>name : password</code> pair that was given. This + * will take all data up to the first occurence of a + * ':' character as the users name and all data after the + * colon as the users password. + */ + private void userpass(){ + userid(); + off++; + password(); + } + + /** + * Extracts the user name from the buffer. This will read up to + * the first occurence of a colon, ':', character as the user + * name. For the BNF syntax of this see RFC 2617. + */ + private void userid(){ + while(off < count){ + char ch = buf[off]; + if(!text(ch) || ch ==':'){ + break; + } + user.append(ch); + off++; + } + + } + + /** + * Extracts the password from the buffer. This will all characters + * from the current offset to the first non text character as the + * password. For the BNF syntax of this see RFC 2617. + */ + private void password() { + while(off < count){ + char ch = buf[off]; + if(!text(ch)){ + break; + } + password.append(ch); + off++; + } + } + + /** + * This is used to remove decode the <code>base64</code> encoding of + * the user name and password. This uses a standart <code>base64</code> + * decoding scheme. + * <p> + * For information on the decoding scheme used for <code>base64</code> + * see the RFC 2045 on MIME, Multipurpose Internet Mail Extensions. + */ + private void decode() { + for(write = read = off; read + 3 < count;) { + while(ready < 4) { + int ch = translate(buf[read++]); + if(ch >= 0) { + four[ready++] = (byte)ch; + } + } + if(four[2] == 65) { + buf[write++] = first(four); + break; + } else if(four[3] == 65) { + buf[write++] = first(four); + buf[write++] = second(four); + break; + } else { + buf[write++] = first(four); + buf[write++] = second(four); + buf[write++] = third(four); + } + ready = 0; + } + count = write; + } + + /** + * This uses a basic translation from the <code>byte</code> character to the + * <code>byte</code> number. + * <p> + * The table for translation the data can be found in RFC 2045 on + * MIME, Multipurpose Internet Mail Extensions. + * + * @param octet this is the octet ttat is to be translated + * + * @return this returns the translated octet + */ + private int translate(int octet) { + if(octet >= 'A' && octet <= 'Z') { + octet = octet - 'A'; + } else if(octet >= 'a' && octet <= 'z') { + octet = (octet - 'a') + 26; + } else if(octet >= '0' && octet <= '9') { + octet = (octet - '0') + 52; + } else if(octet == '+') { + octet = 62; + } else if(octet == '/') { + octet = 63; + } else if(octet == '=') { + octet = 65; + } else { + octet = -1; + } + return octet; + } + + /** + * This is used to extract the <code>byte</code> from the set of four + * <code>bytes</code> given. This method is used to isolate the correct + * bits that corrospond to an actual character withing the + * <code>base64</code> data. + * + * @param four this is the four <code>bytes</code> that the character + * is to be extracted from + * + * @return this returns the character extracted + */ + private char first(byte[] four) { + return (char)(((four[0] & 0x3f) << 2) | ((four[1] & 0x30) >>> 4)); + } + + /** + * This is used to extract the <code>byte</code> from the set of four + * <code>bytes</code> given. This method is used to isolate the correct + * bits that corrospond to an actual character withing the + * <code>base64</code> data. + * + * @param four this is the four <code>bytes</code> that the character + * is to be extracted from + * + * @return this returns the character extracted + + */ + private char second(byte[] four) { + return (char)(((four[1] & 0x0f) << 4) | ((four[2] &0x3c) >>> 2)); + } + + /** + * This is used to extract the <code>byte</code> from the set of four + * <code>bytes</code> given. This method is used to isolate the correct + * bits that corrospond to an actual character withing the + * <code>base64</code> data. + * + * @param four this is the four <code>bytes</code> that the character + * is to be extracted from + * + * @return this returns the character extracted + */ + private char third(byte[] four) { + return (char)(((four[2] & 0x03) << 6) | (four[3] & 0x3f)); + } + + /** + * This is used to determine wheather or not a character is a + * <code>TEXT</code> character according to the HTTP specification, + * that is RFC 2616 specifies a <code>TEXT</code> character as one + * that is any octet except those less than 32 and not 127. + * + * @param c this is the character that is to be determined + * + * @return this returns true if the character is a <code>TEXT</code> + */ + private boolean text(char c){ + return c > 31 && c != 127 && c <= 0xffff; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/QueryParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/QueryParser.java new file mode 100644 index 0000000..56b6788 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/QueryParser.java @@ -0,0 +1,636 @@ +/* + * QueryParser.java December 2002 + * + * Copyright (C) 2002, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import org.simpleframework.common.parse.MapParser; +import org.simpleframework.http.Query; + +import java.net.URLEncoder; +import java.util.Set; + +/** + * The <code>ParameterParser</code> is used to parse data encoded in + * the <code>application/x-www-form-urlencoded</code> MIME type. It + * is also used to parse a query string from a HTTP URL, see RFC 2616. + * The parsed parameters are available through the various methods of + * the <code>org.simpleframework.http.net.Query</code> interface. The + * syntax of the parsed parameters is described below in BNF. + * <pre> + * + * params = *(pair [ "&" params]) + * pair = name "=" value + * name = *(text | escaped) + * value = *(text | escaped) + * escaped = % HEX HEX + * + * </pre> + * This will consume all data found as a name or value, if the data + * is a "+" character then it is replaced with a space character. + * This regards only "=", "&", and "%" as having special values. + * The "=" character delimits the name from the value and the "&" + * delimits the name value pair. The "%" character represents the + * start of an escaped sequence, which consists of two hex digits. + * All escaped sequences are converted to its character value. + * + * @author Niall Gallagher + */ +public class QueryParser extends MapParser<String> implements Query { + + /** + * Used to accumulate the characters for the parameter name. + */ + private Token name; + + /** + * Used to accumulate the characters for the parameter value. + */ + private Token value; + + /** + * Constructor for the <code>ParameterParser</code>. This creates + * an instance that can be use to parse HTML form data and URL + * query strings encoded as application/x-www-form-urlencoded. + * The parsed parameters are made available through the interface + * <code>org.simpleframework.util.net.Query</code>. + */ + public QueryParser(){ + this.name = new Token(); + this.value = new Token(); + } + + /** + * Constructor for the <code>ParameterParser</code>. This creates + * an instance that can be use to parse HTML form data and URL + * query strings encoded as application/x-www-form-urlencoded. + * The parsed parameters are made available through the interface + * <code>org.simpleframework.util.net.Query</code>. + * + * @param text this is the text to parse for the parameters + */ + public QueryParser(String text){ + this(); + parse(text); + } + + /** + * This extracts an integer parameter for the named value. If the + * named parameter does not exist this will return a zero value. + * If however the parameter exists but is not in the format of a + * decimal integer value then this will throw an exception. + * + * @param name the name of the parameter value to retrieve + * + * @return this returns the named parameter value as an integer + */ + public int getInteger(Object name) { + String value = get(name); + + if(value != null) { + return Integer.parseInt(value); + } + return 0; + } + + /** + * This extracts a float parameter for the named value. If the + * named parameter does not exist this will return a zero value. + * If however the parameter exists but is not in the format of a + * floating point number then this will throw an exception. + * + * @param name the name of the parameter value to retrieve + * + * @return this returns the named parameter value as a float + */ + public float getFloat(Object name) { + String value = get(name); + + if(value != null) { + return Float.parseFloat(value); + } + return 0.0f; + } + + /** + * This extracts a boolean parameter for the named value. If the + * named parameter does not exist this will return false otherwise + * the value is evaluated. If it is either <code>true</code> or + * <code>false</code> then those boolean values are returned. + * + * @param name the name of the parameter value to retrieve + * + * @return this returns the named parameter value as an float + */ + public boolean getBoolean(Object name) { + Boolean flag = Boolean.FALSE; + String value = get(name); + + if(value != null) { + flag = Boolean.valueOf(value); + } + return flag.booleanValue(); + } + + + /** + * This initializes the parser so that it can be used several + * times. This clears any previous parameters extracted. This + * ensures that when the next <code>parse(String)</code> is + * invoked the status of the <code>Query</code> is empty. + */ + protected void init(){ + all.clear(); + map.clear(); + name.len = 0; + value.len = 0; + off = 0; + } + + /** + * This performs the actual parsing of the parameter text. The + * parameters parsed from this are taken as "name=value" pairs. + * Multiple pairs within the text are separated by an "&". + * This will parse and insert all parameters into a hashtable. + */ + protected void parse() { + param(); + while(skip("&")){ + param(); + } + } + + /** + * This method adds the name and value to a map so that the next + * name and value can be collected. The name and value are added + * to the map as string objects. Once added to the map the + * <code>Token</code> objects are set to have zero length so they + * can be reused to collect further values. This will add the + * values to the map as an array of type string. This is done so + * that if there are multiple values that they can be stored. + */ + private void insert(){ + if(name.len > 0){ + insert(name,value); + } + name.len = 0; + value.len = 0; + } + + /** + * This will add the given name and value to the parameters map. + * If any previous value of the given name has been inserted + * into the map then this will overwrite that value. This is + * used to ensure that the string value is inserted to the map. + * + * @param name this is the name of the value to be inserted + * @param value this is the value of a that is to be inserted + */ + private void insert(Token name, Token value){ + put(name.toString(), value.toString()); + } + + /** + * This is an expression that is defined by RFC 2396 it is used + * in the definition of a segment expression. This is basically + * a list of chars with escaped sequences. + * <p> + * This method has to ensure that no escaped chars go unchecked. + * This ensures that the read offset does not go out of bounds + * and consequently throw an out of bounds exception. + */ + private void param() { + name(); + if(skip("=")){ /* in case of error*/ + value(); + } + insert(); + } + + /** + * This extracts the name of the parameter from the character + * buffer. The name of a parameter is defined as a set of + * chars including escape sequences. This will extract the + * parameter name and buffer the chars. The name ends when a + * equals character, "=", is encountered. + */ + private void name(){ + int mark = off; + int pos = off; + + while(off < count){ + if(buf[off]=='%'){ /* escaped */ + escape(); + }else if(buf[off]=='=') { + break; + }else if(buf[off]=='+'){ + buf[off] = ' '; + } + buf[pos++] = buf[off++]; + } + name.len = pos - mark; + name.off = mark; + } + + /** + * This extracts a parameter value from a path segment. The + * parameter value consists of a sequence of chars and some + * escape sequences. The parameter value is buffered so that + * the name and values can be paired. The end of the value + * is determined as the end of the buffer or an ampersand. + */ + private void value(){ + int mark = off; + int pos = off; + + while(off < count){ + if(buf[off]=='%'){ /* escaped */ + escape(); + }else if(buf[off]=='+'){ + buf[off] = ' '; + }else if(buf[off]=='&'){ + break; + } + buf[pos++] = buf[off++]; + } + value.len = pos - mark; + value.off = mark; + } + + /** + * This converts an encountered escaped sequence, that is all + * embedded hexidecimal characters into a native UCS character + * value. This does not take any characters from the stream it + * just prepares the buffer with the correct byte. The escaped + * sequence within the URI will be interpreded as UTF-8. + * <p> + * This will leave the next character to read from the buffer + * as the character encoded from the URI. If there is a fully + * valid escaped sequence, that is <code>"%" HEX HEX</code>. + * This decodes the escaped sequence using UTF-8 encoding, all + * encoded sequences should be in UCS-2 to fit in a Java char. + */ + private void escape() { + int peek = peek(off); + + if(!unicode(peek)) { + binary(peek); + } + } + + /** + * This method determines, using a peek character, whether the + * sequence of escaped characters within the URI is binary data. + * If the data within the escaped sequence is binary then this + * will ensure that the next character read from the URI is the + * binary octet. This is used strictly for backward compatible + * parsing of URI strings, binary data should never appear. + * + * @param peek this is the first escaped character from the URI + * + * @return currently this implementation always returns true + */ + private boolean binary(int peek) { + if(off + 2 < count) { + off += 2; + buf[off] =bits(peek); + } + return true; + } + + /** + * This method determines, using a peek character, whether the + * sequence of escaped characters within the URI is in UTF-8. If + * a UTF-8 character can be successfully decoded from the URI it + * will be the next character read from the buffer. This can + * check for both UCS-2 and UCS-4 characters. However, because + * the Java <code>char</code> can only hold UCS-2, the UCS-4 + * characters will have only the low order octets stored. + * <p> + * The WWW Consortium provides a reference implementation of a + * UTF-8 decoding for Java, in this the low order octets in the + * UCS-4 sequence are used for the character. So, in the + * absence of a defined behaviour, the W3C behaviour is assumed. + * + * @param peek this is the first escaped character from the URI + * + * @return this returns true if a UTF-8 character is decoded + */ + private boolean unicode(int peek) { + if((peek & 0x80) == 0x00){ + return unicode(peek, 0); + } + if((peek & 0xe0) == 0xc0){ + return unicode(peek & 0x1f, 1); + } + if((peek & 0xf0) == 0xe0){ + return unicode(peek & 0x0f, 2); + } + if((peek & 0xf8) == 0xf0){ + return unicode(peek & 0x07, 3); + } + if((peek & 0xfc) == 0xf8){ + return unicode(peek & 0x03, 4); + } + if((peek & 0xfe) == 0xfc){ + return unicode(peek & 0x01, 5); + } + return false; + } + + /** + * This method will decode the specified amount of escaped + * characters from the URI and convert them into a single Java + * UCS-2 character. If there are not enough characters within + * the URI then this will return false and leave the URI alone. + * <p> + * The number of characters left is determined from the first + * UTF-8 octet, as specified in RFC 2279, and because this is + * a URI there must that number of <code>"%" HEX HEX</code> + * sequences left. If successful the next character read is + * the UTF-8 sequence decoded into a native UCS-2 character. + * + * @param peek contains the bits read from the first UTF octet + * @param more this specifies the number of UTF octets left + * + * @return this returns true if a UTF-8 character is decoded + */ + private boolean unicode(int peek, int more) { + if(off + more * 3 >= count) { + return false; + } + return unicode(peek,more,off); + } + + /** + * This will decode the specified amount of trailing UTF-8 bits + * from the URI. The trailing bits are those following the first + * UTF-8 octet, which specifies the length, in octets, of the + * sequence. The trailing octets are of the form 10xxxxxx, for + * each of these octets only the last six bits are valid UCS + * bits. So a conversion is basically an accumulation of these. + * <p> + * If at any point during the accumulation of the UTF-8 bits + * there is a parsing error, then parsing is aborted an false + * is returned, as a result the URI is left unchanged. + * + * @param peek bytes that have been accumulated fron the URI + * @param more this specifies the number of UTF octets left + * @param pos this specifies the position the parsing begins + * + * @return this returns true if a UTF-8 character is decoded + */ + private boolean unicode(int peek, int more, int pos) { + while(more-- > 0) { + if(buf[pos] == '%'){ + int next = pos + 3; + int hex = peek(next); + + if((hex & 0xc0) == 0x80){ + peek = (peek<<6)|(hex&0x3f); + pos = next; + continue; + } + } + return false; + } + if(pos + 2 < count) { + off = pos + 2; + buf[off]= bits(peek); + } + return true; + } + + /** + * Defines behaviour for UCS-2 versus UCS-4 conversion from four + * octets. The UTF-8 encoding scheme enables UCS-4 characters to + * be encoded and decodeded. However, Java supports the 16-bit + * UCS-2 character set, and so the 32-bit UCS-4 character set is + * not compatable. This basically decides what to do with UCS-4. + * + * @param data up to four octets to be converted to UCS-2 format + * + * @return this returns a native UCS-2 character from the int + */ + private char bits(int data) { + return (char)data; + } + + /** + * This will return the escape expression specified from the URI + * as an integer value of the hexadecimal sequence. This does + * not make any changes to the buffer it simply checks to see if + * the characters at the position specified are an escaped set + * characters of the form <code>"%" HEX HEX</code>, if so, then + * it will convert that hexadecimal string in to an integer + * value, or -1 if the expression is not hexadecimal. + * + * @param pos this is the position the expression starts from + * + * @return the integer value of the hexadecimal expression + */ + private int peek(int pos) { + if(buf[pos] == '%'){ + if(count <= pos + 2) { + return -1; + } + char high = buf[pos + 1]; + char low = buf[pos + 2]; + + return convert(high, low); + } + return -1; + } + + /** + * This will convert the two hexidecimal characters to a real + * integer value, which is returned. This requires characters + * within the range of 'A' to 'F' and 'a' to 'f', and also + * the digits '0' to '9'. The characters encoded using the + * ISO-8859-1 encoding scheme, if the characters are not with + * in the range specified then this returns -1. + * + * @param high this is the high four bits within the integer + * @param low this is the low four bits within the integer + * + * @return this returns the indeger value of the conversion + */ + private int convert(char high, char low) { + int hex = 0x00; + + if(hex(high) && hex(low)){ + if('A' <= high && high <= 'F'){ + high -= 'A' - 'a'; + } + if(high >= 'a') { + hex ^= (high-'a')+10; + } else { + hex ^= high -'0'; + } + hex <<= 4; + + if('A' <= low && low <= 'F') { + low -= 'A' - 'a'; + } + if(low >= 'a') { + hex ^= (low-'a')+10; + } else { + hex ^= low-'0'; + } + return hex; + } + return -1; + } + + /** + * This is used to determine whether a char is a hexadecimal + * <code>char</code> or not. A hexadecimal character is considered + * to be a character within the range of <code>0 - 9</code> and + * between <code>a - f</code> and <code>A - F</code>. This will + * return <code>true</code> if the character is in this range. + * + * @param ch this is the character which is to be determined here + * + * @return true if the character given has a hexadecimal value + */ + private boolean hex(char ch) { + if(ch >= '0' && ch <= '9') { + return true; + } else if(ch >='a' && ch <= 'f') { + return true; + } else if(ch >= 'A' && ch <= 'F') { + return true; + } + return false; + } + + /** + * This <code>encode</code> method will escape the text that + * is provided. This is used to that the parameter pairs can + * be encoded in such a way that it can be transferred over + * HTTP/1.1 using the ISO-8859-1 character set. + * + * @param text this is the text that is to be escaped + * + * @return the text with % HEX HEX UTF-8 escape sequences + */ + private String encode(String text) { + try { + return URLEncoder.encode(text, "UTF-8"); + }catch(Exception e){ + return text; + } + } + + /** + * This <code>encode</code> method will escape the name=value + * pair provided using the UTF-8 character set. This method + * will ensure that the parameters are encoded in such a way + * that they can be transferred via HTTP in ISO-8859-1. + * + * @param name this is the name of that is to be escaped + * @param value this is the value that is to be escaped + * + * @return the pair with % HEX HEX UTF-8 escape sequences + */ + private String encode(String name, String value) { + return encode(name) + "=" + encode(value); + } + + /** + * This <code>toString</code> method is used to compose an string + * in the <code>application/x-www-form-urlencoded</code> MIME type. + * This will encode the tokens specified in the <code>Set</code>. + * Each name=value pair acquired is converted into a UTF-8 escape + * sequence so that the parameters can be sent in the IS0-8859-1 + * format required via the HTTP/1.1 specification RFC 2616. + * + * @param set this is the set of parameters to be encoded + * + * @return returns a HTTP parameter encoding for the pairs + */ + public String toString(Set set) { + Object[] list = set.toArray(); + String text = ""; + + for(int i = 0; i < list.length; i++){ + String name = list[i].toString(); + String value = get(name); + + if(i > 0) { + text += "&"; + } + text += encode(name, value); + } + return text; + } + + /** + * This <code>toString</code> method is used to compose an string + * in the <code>application/x-www-form-urlencoded</code> MIME type. + * This will iterate over all tokens that have been added to this + * object, either during parsing, or during use of the instance. + * Each name=value pair acquired is converted into a UTF-8 escape + * sequence so that the parameters can be sent in the IS0-8859-1 + * format required via the HTTP/1.1 specification RFC 2616. + * + * @return returns a HTTP parameter encoding for the pairs + */ + public String toString() { + Set set = map.keySet(); + + if(map.size() > 0) { + return toString(set); + } + return ""; + } + + /** + * This is used to mark regions within the buffer that represent + * a valid token for either the name of a parameter or its value. + * This is used as an alternative to the <code>ParseBuffer</code> + * which requires memory to be allocated for storing the data + * read from the buffer. This requires only two integer values. + */ + private class Token { + + /** + * This represents the number of characters in the token. + */ + public int len; + + /** + * This represents the start offset within the buffer. + */ + public int off; + + /** + * In order to represent the <code>Token</code> as a value + * that can be used this converts it to a <code>String</code>. + * If the length of the token is less than or equal to zero + * this will return and empty string for the value. + * + * @return this returns a value representing the token + */ + public String toString() { + if(len <= 0) { + return ""; + } + return new String(buf,off,len); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/parse/ValueParser.java b/simple/simple-http/src/main/java/org/simpleframework/http/parse/ValueParser.java new file mode 100644 index 0000000..99b16b9 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/parse/ValueParser.java @@ -0,0 +1,108 @@ +/* + * ValueParser.java September 2003 + * + * Copyright (C) 2003, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.parse; + +import java.util.List; + +/** + * The <code>ValueParser</code> is used to extract a comma separated + * list of HTTP header values. This will extract values without + * any leading or trailing spaces, which enables the values to be + * used. Listing the values that appear in the header also requires + * that the values are ordered. This orders the values using the + * values that appear with any quality parameter associated with it. + * The quality value is a special parameter that often found in a + * comma separated value list to specify the client preference. + * <pre> + * + * image/gif, image/jpeg, text/html + * image/gif;q=1.0, image/jpeg;q=0.8, image/png; q=1.0,*;q=0.1 + * gzip;q=1.0, identity; q=0.5, *;q=0 + * + * </pre> + * The above lists taken from RFC 2616 provides an example of the + * common form comma separated values take. The first illustrates + * a simple comma delimited list, here the ordering of values is + * determined from left to right. The second and third list have + * quality values associated with them, these are used to specify + * a preference and thus order. + * <p> + * Each value within a list has an implicit quality value of 1.0. + * If the value is explicitly set with a the "q" parameter, then + * the values can range from 1.0 to 0.001. This parser ensures + * that the order of values returned from the <code>list</code> + * method adheres to the optional quality parameters and ensures + * that the quality parameters a removed from the resulting text. + * + * @author Niall Gallagher + */ +public class ValueParser extends ListParser<String> { + + /** + * Constructor for the <code>ValueParser</code>. This creates + * a parser with no initial parse data, if there are headers to + * be parsed then the <code>parse(String)</code> method or + * <code>parse(List)</code> method can be used. This will + * parse a delimited list according so RFC 2616 section 4.2. + */ + public ValueParser(){ + super(); + } + + /** + * Constructor for the <code>ValueParser</code>. This creates + * a parser with the text supplied. This will parse the comma + * separated list according to RFC 2616 section 2.1 and 4.2. + * The tokens can be extracted using the <code>list</code> + * method, which will also sort and trim the tokens. + * + * @param text this is the comma separated list to be parsed + */ + public ValueParser(String text) { + super(text); + } + + /** + * Constructor for the <code>ValueParser</code>. This creates + * a parser with the text supplied. This will parse the comma + * separated list according to RFC 2616 section 2.1 and 4.2. + * The tokens can be extracted using the <code>list</code> + * method, which will also sort and trim the tokens. + * + * @param list a list of comma separated lists to be parsed + */ + public ValueParser(List<String> list) { + super(list); + } + + /** + * This creates a string object using an offset and a length. + * The string is created from the extracted token and the offset + * and length ensure that no leading or trailing whitespace are + * within the created string object. + * + * @param text this is the text buffer to acquire the value from + * @param start the offset within the buffer to take characters + * @param len this is the number of characters within the token + */ + @Override + protected String create(char[] text, int start, int len){ + return new String(text, start, len); + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/BinaryData.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/BinaryData.java new file mode 100644 index 0000000..bea3c63 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/BinaryData.java @@ -0,0 +1,75 @@ +/* + * BinaryData.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>BinaryData</code> object represents a binary payload for + * a WebScoket frame. This can be used to send any type of data. If + * however it is used to send text data then it is decoded as UTF-8. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.DataFrame + */ +public class BinaryData implements Data { + + /** + * This is used to convert the binary payload to text. + */ + private final DataConverter converter; + + /** + * This is the byte array that represents the binary payload. + */ + private final byte[] data; + + /** + * Constructor for the <code>BinaryData</code> object. It requires + * an array of binary data that will be send within a frame. + * + * @param data the byte array representing the frame payload + */ + public BinaryData(byte[] data) { + this.converter = new DataConverter(); + this.data = data; + } + + /** + * This returns the binary payload that is to be sent with a frame. + * It contains no headers or other meta data. If the original data + * was text this converts it to UTF-8. + * + * @return the binary payload to be sent with the frame + */ + public byte[] getBinary() { + return data; + } + + /** + * This returns the text payload that is to be sent with a frame. + * It contains no header information or meta data. Caution should + * be used with this method as binary payloads will encode to + * garbage when decoded as UTF-8. + * + * @return the text payload to be sent with the frame + */ + public String getText() { + return converter.convert(data); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/CloseCode.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/CloseCode.java new file mode 100644 index 0000000..c64c605 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/CloseCode.java @@ -0,0 +1,150 @@ +/* + * CloseCode.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>CloseCode</code> enumerates the closure codes specified in + * RFC 6455. When closing an established connection an endpoint may + * indicate a reason for closure. The interpretation of this reason by + * an endpoint, and the action an endpoint should take given this reason, + * are left undefined by RFC 6455. The specification defines a set of + * status codes and specifies which ranges may be used by extensions, + * frameworks, and end applications. The status code and any associated + * textual message are optional components of a Close frame. + * + * @author niall.gallagher + */ +public enum CloseCode { + + /** + * Indicates the purpose for the connection has been fulfilled. + */ + NORMAL_CLOSURE(1000), + + /** + * Indicates that the server is going down or the client browsed away. + */ + GOING_AWAY(1001), + + /** + * Indicates the connection is terminating due to a protocol error. + */ + PROTOCOL_ERROR(1002), + + /** + * Indicates the connection received a data type it cannot accept. + */ + UNSUPPORTED_DATA(1003), + + /** + * According to RFC 6455 this has been reserved for future use. + */ + RESERVED(1004), + + /** + * Indicates that no status code was present and should not be used. + */ + NO_STATUS_CODE(1005), + + /** + * Indicates an abnormal closure and should not be used. + */ + ABNORMAL_CLOSURE(1006), + + /** + * Indicates that a payload was not consistent with the message type. + */ + INVALID_FRAME_DATA(1007), + + /** + * Indicates an endpoint received a message that violates its policy. + */ + POLICY_VIOLATION(1008), + + /** + * Indicates that a payload is too big to be processed. + */ + TOO_BIG(1009), + + /** + * Indicates that the server did not negotiate an extension properly. + */ + NO_EXTENSION(1010), + + /** + * Indicates an unexpected error within the server. + */ + INTERNAL_SERVER_ERROR(1011), + + /** + * Indicates a validation failure for TLS and should not be used. + */ + TLS_HANDSHAKE_FAILURE(1015); + + /** + * This is the actual integer value representing the code. + */ + public final int code; + + /** + * This is the high order byte for the closure code. + */ + public final int high; + + /** + * This is the low order byte for the closure code. + */ + public final int low; + + /** + * Constructor for the <code>CloseCode</code> object. This is used + * to create a closure code using one of the pre-defined values + * within RFC 6455. + * + * @param code this is the code that is to be used + */ + private CloseCode(int code) { + this.high = code & 0x0f; + this.low = code & 0xf0; + this.code = code; + } + + /** + * This is the data that represents the closure code. The array + * contains the high order byte and the low order byte as taken + * from the pre-defined closure code. + * + * @return a byte array representing the closure code + */ + public byte[] getData() { + return new byte[] { (byte)high, (byte)low }; + } + + + public static CloseCode resolveCode(int high, int low) { + for(CloseCode code : values()) { + if(code.high == high) { + if(code.low == low) { + return code; + } + } + } + return NO_STATUS_CODE; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/Data.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Data.java new file mode 100644 index 0000000..bb79830 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Data.java @@ -0,0 +1,51 @@ +/* + * Data.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>Data</code> interface represents a payload for a WebScoket + * frame. It can hold either binary data or text data. For performance + * binary frames are a better choice as all text frames need to be + * encoded as UTF-8 from the native UCS2 format. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.DataFrame + */ +public interface Data { + + /** + * This returns the binary payload that is to be sent with a frame. + * It contains no headers or other meta data. If the original data + * was text this converts it to UTF-8. + * + * @return the binary payload to be sent with the frame + */ + byte[] getBinary(); + + /** + * This returns the text payload that is to be sent with a frame. + * It contains no header information or meta data. Caution should + * be used with this method as binary payloads will encode to + * garbage when decoded as UTF-8. + * + * @return the text payload to be sent with the frame + */ + String getText(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataConverter.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataConverter.java new file mode 100644 index 0000000..5713fd6 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataConverter.java @@ -0,0 +1,111 @@ +/* + * DataConverter.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>DataConverter</code> object is used to convert binary data + * to text data and vice versa. According to RFC 6455 a particular text + * frame might include a partial UTF-8 sequence; however, the whole + * message MUST contain valid UTF-8. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.DataFrame + */ +public class DataConverter { + + /** + * This is the character encoding used to convert the text data. + */ + private final String charset; + + /** + * Constructor for the <code>DataConverter</code> object. By default + * this uses UTF-8 character encoding to convert text data as this + * is what is required for RFC 6455 section 5.6. + */ + public DataConverter() { + this("UTF-8"); + } + + /** + * Constructor for the <code>DataConverter</code> object. This can be + * used to specific a character encoding other than UTF-8. However it + * is not recommended as RFC 6455 section 5.6 suggests the frame must + * contain valid UTF-8 data. + * + * @param charset the character encoding to be used + */ + public DataConverter(String charset) { + this.charset = charset; + } + + /** + * This method is used to convert text using the character encoding + * specified when constructing the converter. Typically this will use + * UTF-8 as required by RFC 6455. + * + * @param text this is the string to convert to a byte array + * + * @return a byte array decoded using the specified encoding + */ + public byte[] convert(String text) { + try { + return text.getBytes(charset); + } catch(Exception e) { + throw new IllegalStateException("Could not encode text as " + charset, e); + } + } + + /** + * This method is used to convert data using the character encoding + * specified when constructing the converter. Typically this will use + * UTF-8 as required by RFC 6455. + * + * @param text this is the byte array to convert to a string + * + * @return a string encoded using the specified encoding + */ + public String convert(byte[] binary) { + try { + return new String(binary, charset); + } catch(Exception e) { + throw new IllegalStateException("Could not decode data as " + charset, e); + } + } + + /** + * This method is used to convert data using the character encoding + * specified when constructing the converter. Typically this will use + * UTF-8 as required by RFC 6455. + * + * @param text this is the byte array to convert to a string + * @param offset the is the offset to read the bytes from + * @param size this is the number of bytes to be used + * + * @return a string encoded using the specified encoding + */ + public String convert(byte[] binary, int offset, int size) { + try { + return new String(binary, offset, size, charset); + } catch(Exception e) { + throw new IllegalStateException("Could not decode data as " + charset, e); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataFrame.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataFrame.java new file mode 100644 index 0000000..b51cd2b --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/DataFrame.java @@ -0,0 +1,212 @@ +/* + * DataFrame.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>DataFrame</code> object represents a frame as defined in + * RFC 6455. A frame is a very lightweight envelope used to send + * control information and either text or binary user data. Typically + * a frame will represent a single message however, it is possible + * to fragment a single frame up in to several frames. A fragmented + * frame has a specific <code>FrameType</code> indicating that it + * is a continuation frame. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.Data + */ +public class DataFrame implements Frame { + + /** + * This is the type used to determine the intent of the frame. + */ + private final FrameType type; + + /** + * This contains the payload to be sent with the frame. + */ + private final Data data; + + /** + * This determines if the frame is the last of a sequence. + */ + private final boolean last; + + /** + * Constructor for the <code>DataFrame</code> object. This is used + * to create a frame using the specified data and frame type. A + * zero payload is created using this constructor and is suitable + * only for specific control frames such as connection termination. + * + * @param type this is the frame type used for this instance + */ + public DataFrame(FrameType type) { + this(type, new byte[0]); + } + + /** + * Constructor for the <code>DataFrame</code> object. This is used + * to create a frame using the specified data and frame type. In + * some cases a control frame may require a zero length payload. + * + * @param type this is the frame type used for this instance + * @param data this is the payload for this frame + */ + public DataFrame(FrameType type, byte[] data) { + this(type, data, true); + } + + /** + * Constructor for the <code>DataFrame</code> object. This is used + * to create a frame using the specified data and frame type. In + * some cases a control frame may require a zero length payload. + * + * @param type this is the frame type used for this instance + * @param data this is the payload for this frame + * @param last true if this is not a fragment in a sequence + */ + public DataFrame(FrameType type, byte[] data, boolean last) { + this(type, new BinaryData(data), last); + } + + /** + * Constructor for the <code>DataFrame</code> object. This is used + * to create a frame using the specified data and frame type. In + * some cases a control frame may require a zero length payload. + * + * @param type this is the frame type used for this instance + * @param data this is the payload for this frame + */ + public DataFrame(FrameType type, String text) { + this(type, text, true); + } + + /** + * Constructor for the <code>DataFrame</code> object. This is used + * to create a frame using the specified data and frame type. In + * some cases a control frame may require a zero length payload. + * + * @param type this is the frame type used for this instance + * @param data this is the payload for this frame + * @param last true if this is not a fragment in a sequence + */ + public DataFrame(FrameType type, String text, boolean last) { + this(type, new TextData(text), last); + } + + /** + * Constructor for the <code>DataFrame</code> object. This is used + * to create a frame using the specified data and frame type. In + * some cases a control frame may require a zero length payload. + * + * @param type this is the frame type used for this instance + * @param data this is the payload for this frame + */ + public DataFrame(FrameType type, Data data) { + this(type, data, true); + } + + /** + * Constructor for the <code>DataFrame</code> object. This is used + * to create a frame using the specified data and frame type. In + * some cases a control frame may require a zero length payload. + * + * @param type this is the frame type used for this instance + * @param data this is the payload for this frame + * @param last true if this is not a fragment in a sequence + */ + public DataFrame(FrameType type, Data data, boolean last) { + this.data = data; + this.type = type; + this.last = last; + } + + /** + * This is used to determine if the frame is the final frame in + * a sequence of fragments or a whole frame. If this returns false + * then the frame is a continuation from from a sequence of + * fragments, otherwise it is a whole frame or the last fragment. + * + * @return this returns false if the frame is a fragment + */ + public boolean isFinal() { + return last; + } + + /** + * This returns the binary payload that is to be sent with the frame. + * It contains no headers or other meta data. If the original data + * was text this converts it to UTF-8. + * + * @return the binary payload to be sent with the frame + */ + public byte[] getBinary() { + return data.getBinary(); + } + + /** + * This returns the text payload that is to be sent with the frame. + * It contains no header information or meta data. Caution should + * be used with this method as binary payloads will encode to + * garbage when decoded as UTF-8. + * + * @return the text payload to be sent with the frame + */ + public String getText(){ + return data.getText(); + } + + /** + * This method is used to convert from one frame type to another. + * Converting a frame type is useful in scenarios such as when a + * ping needs to respond to a pong or when it is more convenient + * to send a text frame as binary. + * + * @param type this is the frame type to convert to + * + * @return a new frame using the specified frame type + */ + public Frame getFrame(FrameType type) { + return new DataFrame(type, data, last); + } + + /** + * This is used to determine the type of frame. Interpretation of + * this type is outlined in RFC 6455 and can be loosely categorised + * as control frames and either data or binary frames. + * + * @return this returns the type of frame that this represents + */ + public FrameType getType(){ + return type; + } + + /** + * This returns the text payload that is to be sent with the frame. + * It contains no header information or meta data. Caution should + * be used with this method as binary payloads will encode to + * garbage when decoded as UTF-8. + * + * @return the text payload to be sent with the frame + */ + @Override + public String toString() { + return getText(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/Frame.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Frame.java new file mode 100644 index 0000000..7f5ad0f --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Frame.java @@ -0,0 +1,85 @@ +/* + * Frame.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>Frame</code> interface represents a frame as defined in + * RFC 6455. A frame is a very lightweight envelope used to send + * control information and either text or binary user data. Typically + * a frame will represent a single message however, it is possible + * to fragment a single frame up in to several frames. A fragmented + * frame has a specific <code>FrameType</code> indicating that it + * is a continuation frame. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.DataFrame + */ +public interface Frame { + + /** + * This is used to determine if the frame is the final frame in + * a sequence of fragments or a whole frame. If this returns false + * then the frame is a continuation from from a sequence of + * fragments, otherwise it is a whole frame or the last fragment. + * + * @return this returns false if the frame is a fragment + */ + boolean isFinal(); + + /** + * This returns the binary payload that is to be sent with the frame. + * It contains no headers or other meta data. If the original data + * was text this converts it to UTF-8. + * + * @return the binary payload to be sent with the frame + */ + byte[] getBinary(); + + /** + * This returns the text payload that is to be sent with the frame. + * It contains no header information or meta data. Caution should + * be used with this method as binary payloads will encode to + * garbage when decoded as UTF-8. + * + * @return the text payload to be sent with the frame + */ + String getText(); + + /** + * This method is used to convert from one frame type to another. + * Converting a frame type is useful in scenarios such as when a + * ping needs to respond to a pong or when it is more convenient + * to send a text frame as binary. + * + * @param type this is the frame type to convert to + * + * @return a new frame using the specified frame type + */ + Frame getFrame(FrameType type); + + /** + * This is used to determine the type of frame. Interpretation of + * this type is outlined in RFC 6455 and can be loosely categorised + * as control frames and either data or binary frames. + * + * @return this returns the type of frame that this represents + */ + FrameType getType(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameChannel.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameChannel.java new file mode 100644 index 0000000..bcacc43 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameChannel.java @@ -0,0 +1,117 @@ +/* + * FrameChannel.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +import java.io.IOException; + +/** + * The <code>FrameChannel</code> represents a full duplex communication + * channel as defined by RFC 6455. Any instance of this will provide + * a means to perform asynchronous writes and reads to a remote client + * using a lightweight framing protocol. A frame is a finite length + * sequence of bytes that can hold either text or binary data. Also, + * control frames are used to perform heartbeat monitoring and closure. + * <p> + * For convenience frames can be consumed from the socket via a + * callback to a registered listener. This avoids having to poll each + * socket for data and provides a asynchronous event driven model of + * communication, which greatly reduces overhead and complication. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.FrameListener + * @see org.simpleframework.http.socket.Frame + */ +public interface FrameChannel { + + /** + * This is used to send data to the connected client. To prevent + * an application code from causing resource issues this will block + * as soon as a configured linked list of mapped memory buffers has + * been exhausted. Caution should be taken when writing a broadcast + * implementation that can write to multiple sockets as a badly + * behaving socket that has filled its output buffering capacity + * can cause congestion. + * + * @param data this is the data that is to be sent + */ + void send(byte[] data) throws IOException; + + /** + * This is used to send text to the connected client. To prevent + * an application code from causing resource issues this will block + * as soon as a configured linked list of mapped memory buffers has + * been exhausted. Caution should be taken when writing a broadcast + * implementation that can write to multiple sockets as a badly + * behaving socket that has filled its output buffering capacity + * can cause congestion. + * + * @param text this is the text that is to be sent + */ + void send(String text) throws IOException; + + /** + * This is used to send data to the connected client. To prevent + * an application code from causing resource issues this will block + * as soon as a configured linked list of mapped memory buffers has + * been exhausted. Caution should be taken when writing a broadcast + * implementation that can write to multiple sockets as a badly + * behaving socket that has filled its output buffering capacity + * can cause congestion. + * + * @param frame this is the frame that is to be sent + */ + void send(Frame frame) throws IOException; + + /** + * This is used to register a <code>FrameListener</code> to this + * instance. The registered listener will receive all user frames + * and control frames sent from the client. Also, when the frame + * is closed or when an unexpected error occurs the listener is + * notified. Any number of listeners can be registered at any time. + * + * @param listener this is the listener that is to be registered + */ + void register(FrameListener listener) throws IOException; + + /** + * This is used to remove a <code>FrameListener</code> from this + * instance. After removal the listener will no longer receive + * any user frames or control messages from this specific instance. + * + * @param listener this is the listener to be removed + */ + void remove(FrameListener listener) throws IOException; + + /** + * This is used to close the connection with a specific reason. + * The close reason will be sent as a control frame before the + * TCP connection is terminated. + * + * @param reason the reason for closing the connection + */ + void close(Reason reason) throws IOException; + + /** + * This is used to close the connection without a specific reason. + * The close reason will be sent as a control frame before the + * TCP connection is terminated. + */ + void close() throws IOException; +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameListener.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameListener.java new file mode 100644 index 0000000..6892e9c --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameListener.java @@ -0,0 +1,64 @@ +/* + * FrameListener.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>FrameListener</code> is used to listen for incoming frames + * on a <code>WebSocket</code>. Any number of listeners can listen on + * a single web socket and it will receive all incoming events. For + * consistency this interface is modelled on the WebSocket API as + * defined by W3C Candidate Recommendation as of 20 September 2012. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.FrameChannel + */ +public interface FrameListener { + + /** + * This is called when a new frame arrives on the WebSocket. It + * will receive control frames as well as binary and text user + * frames. Control frames should not be acted on or responded + * to as they are provided for informational purposes only. + * + * @param session this is the associated session + * @param frame this is the frame that has been received + */ + void onFrame(Session session, Frame frame); + + /** + * This is called when an error occurs on the WebSocket. After + * an error the connection it is closed with an opcode indicating + * an internal server error. + * + * @param session this is the associated session + * @param frame this is the exception that has been thrown + */ + void onError(Session session, Exception cause); + + /** + * This is called when the connection is closed from the other + * side. Typically a frame with an opcode of close is sent + * before the close callback is issued. + * + * @param session this is the associated session + * @param reason this is the reason the connection was closed + */ + void onClose(Session session, Reason reason); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameType.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameType.java new file mode 100644 index 0000000..8237701 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/FrameType.java @@ -0,0 +1,142 @@ +/* + * FrameType.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>FrameType</code> represents the set of opcodes defined + * in RFC 6455. The base framing protocol uses a opcode to define the + * interpretation of the payload data for the frame. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.Frame + */ +public enum FrameType { + + /** + * A continuation frame identifies a fragment from a larger message. + */ + CONTINUATION(0x00), + + /** + * A text frame identifies a message that contains UTF-8 text data. + */ + TEXT(0x01), + + /** + * A binary frame identifies a message that contains binary data. + */ + BINARY(0x02), + + /** + * A close frame identifies a frame used to terminate a connection. + */ + CLOSE(0x08), + + /** + * A ping frame is a heartbeat used to determine connection health. + */ + PING(0x09), + + /** + * A pong frame is sent is sent in response to a ping frame. + */ + PONG(0x0a); + + /** + * This is the integer value for the opcode. + */ + public final int code; + + /** + * Constructor for the <code>Frame</code> type enumeration. This is + * given the opcode that is used to identify a specific frame type. + * + * @param code this is the opcode representing the frame type + */ + private FrameType(int code) { + this.code = code; + } + + /** + * This is used to determine if a frame is a text frame. It can be + * useful to know if a frame is a user based frame as it reduces + * the need to convert from or to certain character sets. + * + * @return this returns true if the frame represents a text frame + */ + public boolean isText() { + return this == TEXT; + } + + /** + * This is used to determine if a frame is a close frame. A close + * frame contains an optional payload, which if present contains + * an error code in network byte order in the first two bytes, + * followed by an optional UTF-8 text reason of the closure. + * + * @return this returns true if the frame represents a close frame + */ + public boolean isClose() { + return this == CLOSE; + } + + /** + * This is used to determine if a frame is a pong frame. A pong + * frame is sent in response to a ping and is used to determine if + * a WebSocket connection is still active and healthy. + * + * @return this returns true if the frame represents a pong frame + */ + public boolean isPong() { + return this == PONG; + } + + /** + * This is used to determine if a frame is a ping frame. A ping + * frame is sent to check if a WebSocket connection is still healthy. + * A connection is determined healthy if it responds with a pong + * frame is a reasonable length of time. + * + * @return this returns true if the frame represents a ping frame + */ + public boolean isPing() { + return this == PING; + } + + /** + * This is used to acquire the frame type given an opcode. If no + * frame type can be determined from the opcode provided then this + * will return a null value. + * + * @param octet this is the octet representing the opcode + * + * @return this returns the frame type from the opcode + */ + public static FrameType resolveType(int octet) { + int value = octet & 0xff; + + for(FrameType code : values()) { + if(code.code == value) { + return code; + } + } + return null; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/Reason.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Reason.java new file mode 100644 index 0000000..c7438e5 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Reason.java @@ -0,0 +1,97 @@ +/* + * Reason.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>Reason</code> object is used to hold a textual reason + * for connection closure and an RFC 6455 defined code. When a + * connection is to be closed a control frame with an opcode of + * close is sent with the text reason, if one is provided. + * + * @author Niall Gallagher + */ +public class Reason { + + /** + * This is the close code to be sent with a control frame. + */ + private final CloseCode code; + + /** + * This is the textual description of the close reason. + */ + private final String text; + + /** + * Constructor for the <code>Reason</code> object. This is used + * to create a reason and a textual description of that reason + * to be delivered as a control frame. + * + * @param code this is the code to be sent with the frame + */ + public Reason(CloseCode code) { + this(code, null); + } + + /** + * Constructor for the <code>Reason</code> object. This is used + * to create a reason and a textual description of that reason + * to be delivered as a control frame. + * + * @param code this is the code to be sent with the frame + * @param text this is textual description of the close reason + */ + public Reason(CloseCode code, String text) { + this.code = code; + this.text = text; + } + + /** + * This is used to get the RFC 6455 code describing the type + * of close event. It is the code that should be used by + * applications to determine why the connection was terminated. + * + * @return returns the close code for the connection + */ + public CloseCode getCode() { + return code; + } + + /** + * This is used to get the textual description for the closure. + * In many scenarios there will be no textual reason as it is + * an optional attribute. + * + * @return this returns the description for the closure + */ + public String getText() { + return text; + } + + /** + * This is used to provide a textual representation of the reason. + * For consistency this will only return the enumerated value for + * the close code, or if none exists a "null" text string. + * + * @return this returns a string representation of the reason + */ + public String toString() { + return String.valueOf(code); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/Session.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Session.java new file mode 100644 index 0000000..7c9a7db --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/Session.java @@ -0,0 +1,91 @@ +/* + * Session.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +import java.util.Map; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +/** + * The <code>Session</code> object represents a simple WebSocket session + * that contains the connection handshake details and the actual socket. + * In order to determine how the session should be interacted with the + * protocol is conveniently exposed, however all attributes of the + * original HTTP request are available. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.FrameChannel + */ +public interface Session { + + /** + * This can be used to retrieve the response attributes. These can + * be used to keep state with the response when it is passed to + * other systems for processing. Attributes act as a convenient + * model for storing objects associated with the response. This + * also inherits attributes associated with the client connection. + * + * @return the attributes of that have been set on the request + */ + Map getAttributes(); + + /** + * This is used as a shortcut for acquiring attributes for the + * response. This avoids acquiring the attribute <code>Map</code> + * in order to retrieve the attribute directly from that object. + * The attributes contain data specific to the response. + * + * @param key this is the key of the attribute to acquire + * + * @return this returns the attribute for the specified name + */ + Object getAttribute(Object key); + + /** + * Provides a <code>FrameChannel</code> that can be used to communicate + * with the connected client. Communication is full duplex and also + * asynchronous through the use of a <code>FrameListener</code> that + * can be registered with the channel. + * + * @return a web socket for full duplex communication + */ + FrameChannel getChannel(); + + /** + * Provides the <code>Request</code> used to initiate the session. + * This is useful in establishing the identity of the user, acquiring + * an security information and also for determining the request path + * that was used, which be used to establish context. + * + * @return the request used to initiate the session + */ + Request getRequest(); + + /** + * Provides the <code>Response</code> used to establish the session + * with the remote client. This is useful in establishing the protocol + * used to create the session and also for determining various other + * useful contextual information. + * + * @return the response used to establish the session + */ + Response getResponse(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/TextData.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/TextData.java new file mode 100644 index 0000000..24ee97d --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/TextData.java @@ -0,0 +1,75 @@ +/* + * TextData.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket; + +/** + * The <code>TextData</code> object represents a text payload for + * a WebScoket frame. This can be used to send any type of data. If + * however it is used to send binary data then it is encoded as UTF-8. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.DataFrame + */ +public class TextData implements Data { + + /** + * This is used to convert the text payload to a byte array. + */ + private final DataConverter converter; + + /** + * This is the text string representing a frame payload. + */ + private final String data; + + /** + * Constructor for the <code>TextData</code> object. It requires + * an text string that will be sent as UTF-8 within a frame. + * + * @param data the text string representing the frame payload + */ + public TextData(String data) { + this.converter = new DataConverter(); + this.data = data; + } + + /** + * This returns the binary payload that is to be sent with a frame. + * It contains no headers or other meta data. If the original data + * was text this converts it to UTF-8. + * + * @return the binary payload to be sent with the frame + */ + public byte[] getBinary() { + return converter.convert(data); + } + + /** + * This returns the text payload that is to be sent with a frame. + * It contains no header information or meta data. Caution should + * be used with this method as binary payloads will encode to + * garbage when decoded as UTF-8. + * + * @return the text payload to be sent with the frame + */ + public String getText() { + return data; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/AcceptToken.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/AcceptToken.java new file mode 100644 index 0000000..2fe2521 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/AcceptToken.java @@ -0,0 +1,127 @@ +/* + * AcceptToken.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_KEY; + +import java.io.IOException; +import java.security.MessageDigest; + +import org.simpleframework.common.encode.Base64Encoder; +import org.simpleframework.http.Request; + +/** + * The <code>AcceptToken</code> is used to create a unique token based + * on a random key sent by the client. This is used to prove that the + * handshake was received, the server has to take two pieces of + * information and combine them to form a response. The first piece + * of information comes from the <code>Sec-WebSocket-Key</code> header + * field in the client handshake, the second is the globally unique + * identifier <code>258EAFA5-E914-47DA-95CA-C5AB0DC85B11</code>. Both + * are concatenated and an SHA-1 has is generated and used in the + * session initiating response. + * + * @author Niall Gallagher + */ +class AcceptToken { + + /** + * This is the globally unique identifier used in the handshake. + */ + private static final byte[] MAGIC = { + '2', '5', '8', 'E', 'A', 'F', 'A', '5', '-', + 'E', '9', '1', '4', '-', '4', '7', 'D', 'A', + '-', '9', '5', 'C', 'A', '-', 'C', '5', 'A', + 'B', '0', 'D', 'C', '8', '5', 'B', '1', '1' }; + + /** + * This is used to generate the SHA-1 has from the user key. + */ + private final MessageDigest digest; + + /** + * This is the original request used to initiate the session. + */ + private final Request request; + + /** + * This is the character encoding to decode the key with. + */ + private final String charset; + + /** + * Constructor for the <code>AcceptToken</code> object. This is + * to create an object that can generate a token from the client + * key available from the <code>Sec-WebSocket-Key</code> header. + * + * @param request this is the session initiating request + */ + public AcceptToken(Request request) throws Exception { + this(request, "SHA-1"); + } + + /** + * Constructor for the <code>AcceptToken</code> object. This is + * to create an object that can generate a token from the client + * key available from the <code>Sec-WebSocket-Key</code> header. + * + * @param request this is the session initiating request + * @param algorithm the algorithm used to create the token + */ + public AcceptToken(Request request, String algorithm) throws Exception { + this(request, algorithm, "UTF-8"); + } + + /** + * Constructor for the <code>AcceptToken</code> object. This is + * to create an object that can generate a token from the client + * key available from the <code>Sec-WebSocket-Key</code> header. + * + * @param request this is the session initiating request + * @param algorithm the algorithm used to create the token + * @param charset the encoding used to decode the client key + */ + public AcceptToken(Request request, String algorithm, String charset) throws Exception { + this.digest = MessageDigest.getInstance(algorithm); + this.request = request; + this.charset = charset; + } + + /** + * This is used to create the required accept token for the session + * initiating response. The resulting token is a SHA-1 digest of + * the <code>Sec-WebSocket-Key</code> a globally unique identifier + * defined in RFC 6455 all encoded in base64. + * + * @return the accept token for the session initiating response + */ + public String create() throws IOException { + String value = request.getValue(SEC_WEBSOCKET_KEY); + byte[] data = value.getBytes(charset); + + if (data.length > 0) { + digest.update(data); + digest.update(MAGIC); + } + byte[] digested = digest.digest(); + char[] text = Base64Encoder.encode(digested); + + return new String(text); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/DirectRouter.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/DirectRouter.java new file mode 100644 index 0000000..0c09063 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/DirectRouter.java @@ -0,0 +1,107 @@ +/* + * DirectRouter.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_PROTOCOL; +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_VERSION; +import static org.simpleframework.http.Protocol.UPGRADE; +import static org.simpleframework.http.Protocol.WEBSOCKET; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +/** + * The <code>DirectRouter</code> object is used to create a router + * that uses a single service. Typically this is used by simpler + * servers that wish to expose a single sub-protocol to clients. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.RouterContainer + */ +public class DirectRouter implements Router { + + /** + * The service used by this router instance. + */ + private final Service service; + + /** + * The protocol used or null if none was specified. + */ + private final String protocol; + + /** + * Constructor for the <code>DirectRouter</code> object. This + * is used to create an object that will select a single service. + * Creating an instance with this constructor means that the + * protocol header will not be set. + * + * @param service this is the service used by this instance + * @param protocol the protocol used by this router or null + */ + public DirectRouter(Service service) { + this(service, null); + } + + /** + * Constructor for the <code>DirectRouter</code> object. This + * is used to create an object that will select a single service. + * If the protocol specified is null then the response to the + * session initiation will contain null for the protocol header. + * + * @param service this is the service used by this instance + * @param protocol the protocol used by this router or null + */ + public DirectRouter(Service service, String protocol) { + this.protocol = protocol; + this.service = service; + } + + /** + * This is used to route an incoming request to a service if + * the request represents a WebSocket handshake as defined by + * RFC 6455. If the request is not a session initiating handshake + * then this will return a null value to allow it to be processed + * by some other part of the server. + * + * @param request this is the request to use for routing + * @param response this is the response to establish the session + * + * @return a service that can be used to process the session + */ + public Service route(Request request, Response response) { + String token = request.getValue(UPGRADE); + + if(token != null) { + if(token.equalsIgnoreCase(WEBSOCKET)) { + String version = request.getValue(SEC_WEBSOCKET_VERSION); + + if(version != null) { + response.setValue(SEC_WEBSOCKET_VERSION, version); + } + if(protocol != null) { + response.setValue(SEC_WEBSOCKET_PROTOCOL, protocol); + } + return service; + } + } + return null; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameBuilder.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameBuilder.java new file mode 100644 index 0000000..6ab224a --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameBuilder.java @@ -0,0 +1,118 @@ +/* + * FrameBuilder.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import java.util.Arrays; + +import org.simpleframework.http.socket.DataConverter; +import org.simpleframework.http.socket.DataFrame; +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.FrameType; + +/** + * The <code>FrameBuilder</code> object is used to create an object + * that interprets a frame header to produce frame objects. For + * efficiency this converts binary data to the native frame data + * type, which avoids memory churn. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.FrameConsumer + */ +class FrameBuilder { + + /** + * This converts binary data to a UTF-8 string for text frames. + */ + private final DataConverter converter; + + /** + * This is used to determine the type of frames to create. + */ + private final FrameHeader header; + + /** + * Constructor for the <code>FrameBuilder</code> object. This acts + * as a factory for frame objects by using the provided header to + * determine the frame type to be created. + * + * @param header the header used to determine the frame type + */ + public FrameBuilder(FrameHeader header) { + this.converter = new DataConverter(); + this.header = header; + } + + /** + * This is used to create a frame object to represent the data that + * has been consumed. The frame created will contain either a copy of + * the provided byte buffer or a text string encoded in UTF-8. To + * avoid memory churn this method should be used sparingly. + * + * @return this returns a frame created from the consumed bytes + */ + public Frame create(byte[] data, int count) { + FrameType type = header.getType(); + + if(type.isText()) { + return createText(data, count); + } + return createBinary(data, count); + } + + /** + * This is used to create a frame object from the provided data. + * The resulting frame will contain a UTF-8 encoding of the data + * to ensure that data conversion needs to be performed only once. + * + * @param data this is the data to convert to a new frame + * @param count this is the number of bytes in the frame + * + * @return a new frame containing the text + */ + private Frame createText(byte[] data, int count) { + FrameType type = header.getType(); + String text = converter.convert(data, 0, count); + + if(header.isFinal()) { + return new DataFrame(type, text, true); + } + return new DataFrame(type, text, false); + } + + /** + * This is used to create a frame object from the provided data. + * The resulting frame will contain a copy of the data to ensure + * that the frame is immutable. + * + * @param data this is the data to convert to a new frame + * @param count this is the number of bytes in the frame + * + * @return a new frame containing a copy of the provided data + */ + private Frame createBinary(byte[] data, int count) { + FrameType type = header.getType(); + byte[] copy = Arrays.copyOf(data, count); + + if(header.isFinal()) { + return new DataFrame(type, copy, true); + } + return new DataFrame(type, copy, false); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameCollector.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameCollector.java new file mode 100644 index 0000000..8987620 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameCollector.java @@ -0,0 +1,179 @@ +/* + * FrameCollector.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.socket.service.ServiceEvent.ERROR; + +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; + +import org.simpleframework.http.Request; +import org.simpleframework.http.socket.FrameListener; +import org.simpleframework.http.socket.Session; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteCursor; +import org.simpleframework.transport.reactor.Operation; +import org.simpleframework.transport.reactor.Reactor; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>FrameCollector</code> operation is used to collect frames + * from a channel and dispatch them to a <code>FrameListener</code>. + * To ensure that stale connections do not linger any connection that + * does not send a control ping or pong frame within two minutes will + * be terminated and the close control frame will be sent. + * + * @author Niall Gallagher + */ +class FrameCollector implements Operation { + + /** + * This decodes the frame bytes from the channel and processes it. + */ + private final FrameProcessor processor; + + /** + * This is the cursor used to maintain a stream seek position. + */ + private final ByteCursor cursor; + + /** + * This is the underlying channel for this frame collector. + */ + private final Channel channel; + + /** + * This is the reactor used to schedule this operation for reads. + */ + private final Reactor reactor; + + /** + * This is the tracer that is used to trace the frame collection. + */ + private final Trace trace; + + /** + * Constructor for the <code>FrameCollector</code> object. This is + * used to create a collector that will process and dispatch web + * socket frames as defined by RFC 6455. + * + * @param encoder this is the encoder used to send messages + * @param session this is the web socket session + * @param channel this is the underlying TCP communication channel + * @param reactor this is the reactor used for read notifications + */ + public FrameCollector(FrameEncoder encoder, Session session, Request request, Reactor reactor) { + this.processor = new FrameProcessor(encoder, session, request); + this.channel = request.getChannel(); + this.cursor = channel.getCursor(); + this.trace = channel.getTrace(); + this.reactor = reactor; + } + + /** + * This is used to acquire the trace object that is associated + * with the operation. A trace object is used to collection details + * on what operations are being performed. For instance it may + * contain information relating to I/O events or errors. + * + * @return this returns the trace associated with this operation + */ + public Trace getTrace() { + return trace; + } + + /** + * This is the channel associated with this collector. This is used + * to register for notification of read events. If at any time the + * remote endpoint is closed then this will cause the collector + * to perform a final execution before closing. + * + * @return this returns the selectable TCP channel + */ + public SelectableChannel getChannel() { + return channel.getSocket(); + } + + /** + * This is used to register a <code>FrameListener</code> to this + * instance. The registered listener will receive all user frames + * and control frames sent from the client. Also, when the frame + * is closed or when an unexpected error occurs the listener is + * notified. Any number of listeners can be registered at any time. + * + * @param listener this is the listener that is to be registered + */ + public void register(FrameListener listener) { + processor.register(listener); + } + + /** + * This is used to remove a <code>FrameListener</code> from this + * instance. After removal the listener will no longer receive + * any user frames or control messages from this specific instance. + * + * @param listener this is the listener to be removed + */ + public void remove(FrameListener listener) { + processor.remove(listener); + } + + /** + * This is used to execute the collection operation. Collection is + * done by reading the frame header from the incoming data, once + * consumed the remainder of the frame is collected until such + * time as it has been fully consumed. When consumed it will be + * dispatched to the registered frame listeners. + */ + public void run() { + try { + processor.process(); + + if(cursor.isOpen()) { + reactor.process(this, SelectionKey.OP_READ); + } else { + processor.close(); + } + } catch(Exception cause) { + trace.trace(ERROR, cause); + + try { + processor.failure(cause); + } catch(Exception fatal) { + trace.trace(ERROR, fatal); + } finally { + channel.close(); + } + } + } + + /** + * This is called when a read operation has timed out. To ensure + * that stale channels do not remain registered they are cleared + * out with this method and a close frame is sent if possible. + */ + public void cancel() { + try{ + processor.close(); + } catch(Exception cause) { + trace.trace(ERROR, cause); + channel.close(); + } + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConnection.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConnection.java new file mode 100644 index 0000000..b904130 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConnection.java @@ -0,0 +1,214 @@ +/* + * FrameConnection.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.socket.CloseCode.NORMAL_CLOSURE; +import static org.simpleframework.http.socket.service.ServiceEvent.OPEN_SOCKET; + +import java.io.IOException; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.FrameListener; +import org.simpleframework.http.socket.Reason; +import org.simpleframework.http.socket.Session; +import org.simpleframework.http.socket.FrameChannel; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteWriter; +import org.simpleframework.transport.reactor.Reactor; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>FrameConnection</code> represents a connection that can + * send and receivd WebSocket frames. Any instance of this will provide + * a means to perform asynchronous writes and reads to a remote client + * using a lightweight framing protocol. A frame is a finite length + * sequence of bytes that can hold either text or binary data. Also, + * control frames are used to perform heartbeat monitoring and closure. + * <p> + * For convenience frames can be consumed from the socket via a + * callback to a registered listener. This avoids having to poll each + * socket for data and provides a asynchronous event driven model of + * communication, which greatly reduces overhead and complication. + * + * @author Niall Gallagher + */ +class FrameConnection implements FrameChannel { + + /** + * The collector is used to collect frames from the TCP channel. + */ + private final FrameCollector operation; + + /** + * This encoder is used to encode data as RFC 6455 frames. + */ + private final FrameEncoder encoder; + + /** + * This is the sender used to send frames over the channel. + */ + private final ByteWriter writer; + + /** + * This is the session object that has a synchronized channel. + */ + private final Session session; + + /** + * This is the underlying TCP channel that frames are sent over. + */ + private final Channel channel; + + /** + * The reason that is sent if at any time the channel is closed. + */ + private final Reason reason; + + /** + * This is used to trace all events that occur on the channel. + */ + private final Trace trace; + + /** + * Constructor for the <code>FrameConnection</code> object. This is used + * to create a channel that can read and write frames over a TCP + * channel. For asynchronous read and dispatch operations this will + * produce an operation to collect and process RFC 6455 frames. + * + * @param request this is the initiating request for the WebSocket + * @param response this is the initiating response for the WebSocket + * @param reactor this is the reactor used to process frames + */ + public FrameConnection(Request request, Response response, Reactor reactor) { + this.encoder = new FrameEncoder(request); + this.session = new ServiceSession(this, request, response); + this.operation = new FrameCollector(encoder, session, request, reactor); + this.reason = new Reason(NORMAL_CLOSURE); + this.channel = request.getChannel(); + this.writer = channel.getWriter(); + this.trace = channel.getTrace(); + } + + /** + * This is used to open the channel and begin consuming frames. This + * will also return the session that contains the details for the + * created WebSocket such as the initiating request and response as + * well as the <code>FrameChannel</code> object. + * + * @return the session associated with the WebSocket + */ + public Session open() throws IOException { + trace.trace(OPEN_SOCKET); + operation.run(); + return session; + } + + /** + * This is used to register a <code>FrameListener</code> to this + * instance. The registered listener will receive all user frames + * and control frames sent from the client. Also, when the frame + * is closed or when an unexpected error occurs the listener is + * notified. Any number of listeners can be registered at any time. + * + * @param listener this is the listener that is to be registered + */ + public void register(FrameListener listener) throws IOException { + operation.register(listener); + } + + /** + * This is used to remove a <code>FrameListener</code> from this + * instance. After removal the listener will no longer receive + * any user frames or control messages from this specific instance. + * + * @param listener this is the listener to be removed + */ + public void remove(FrameListener listener) throws IOException { + operation.remove(listener); + } + + /** + * This is used to send data to the connected client. To prevent + * an application code from causing resource issues this will block + * as soon as a configured linked list of mapped memory buffers has + * been exhausted. Caution should be taken when writing a broadcast + * implementation that can write to multiple sockets as a badly + * behaving socket that has filled its output buffering capacity + * can cause congestion. + * + * @param data this is the data that is to be sent + */ + public void send(byte[] data) throws IOException { + encoder.encode(data); + } + + /** + * This is used to send text to the connected client. To prevent + * an application code from causing resource issues this will block + * as soon as a configured linked list of mapped memory buffers has + * been exhausted. Caution should be taken when writing a broadcast + * implementation that can write to multiple sockets as a badly + * behaving socket that has filled its output buffering capacity + * can cause congestion. + * + * @param text this is the text that is to be sent + */ + public void send(String text) throws IOException { + encoder.encode(text); + } + + /** + * This is used to send data to the connected client. To prevent + * an application code from causing resource issues this will block + * as soon as a configured linked list of mapped memory buffers has + * been exhausted. Caution should be taken when writing a broadcast + * implementation that can write to multiple sockets as a badly + * behaving socket that has filled its output buffering capacity + * can cause congestion. + * + * @param frame this is the frame that is to be sent + */ + public void send(Frame frame) throws IOException { + encoder.encode(frame); + } + + /** + * This is used to close the connection with a specific reason. + * The close reason will be sent as a control frame before the + * TCP connection is terminated. + * + * @param reason the reason for closing the connection + */ + public void close(Reason reason) throws IOException { + encoder.encode(reason); + writer.close(); + } + + /** + * This is used to close the connection without a specific reason. + * The close reason will be sent as a control frame before the + * TCP connection is terminated. + */ + public void close() throws IOException { + encoder.encode(reason); + writer.close(); + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConsumer.java new file mode 100644 index 0000000..579d6ef --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameConsumer.java @@ -0,0 +1,162 @@ +/* + * FrameConsumer.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import java.io.IOException; + +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.FrameType; +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>FrameConsumer</code> object is used to read a WebSocket + * frame as defined by RFC 6455. This is a state machine that can read + * the data one byte at a time until the entire frame has been consumed. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.FrameCollector + */ +class FrameConsumer { + + /** + * This is used to consume the header part of the frame. + */ + private FrameHeaderConsumer header; + + /** + * This is used to interpret the header and create a frame. + */ + private FrameBuilder builder; + + /** + * This is used to buffer the bytes that form the frame. + */ + private byte[] buffer; + + /** + * This is a count of the payload bytes currently consumed. + */ + private int count; + + /** + * Constructor for the <code>FrameConsumer</code> object. This is + * used to create a consumer to read the bytes that form the frame + * from an underlying TCP connection. Internally a buffer is created + * to allow bytes to be consumed and collected in chunks. + */ + public FrameConsumer() { + this.header = new FrameHeaderConsumer(); + this.builder = new FrameBuilder(header); + this.buffer = new byte[2048]; + } + + /** + * This is used to determine the type of frame. Interpretation of + * this type is outlined in RFC 6455 and can be loosely categorised + * as control frames and either data or binary frames. + * + * @return this returns the type of frame that this represents + */ + public FrameType getType() { + return header.getType(); + } + + /** + * This is used to create a frame object to represent the data that + * has been consumed. The frame created will make a copy of the + * internal byte buffer so this method should be used sparingly. + * + * @return this returns a frame created from the consumed bytes + */ + public Frame getFrame() { + return builder.create(buffer, count); + } + + /** + * This consumes frame bytes using the provided cursor. The consumer + * acts as a state machine by consuming the data as that data + * becomes available, this allows it to consume data asynchronously + * and dispatch once the whole frame has been consumed. + * + * @param cursor the cursor to consume the frame data from + */ + public void consume(ByteCursor cursor) throws IOException { + while (cursor.isReady()) { + if(!header.isFinished()) { + header.consume(cursor); + } + if(header.isFinished()) { + int length = header.getLength(); + + if(count <= length) { + if(buffer.length < length) { + buffer = new byte[length]; + } + if(count < length) { + int size = cursor.read(buffer, count, length - count); + + if(size == -1) { + throw new IOException("Could only read " + count + " of length " + length); + } + count += size; + } + if(count == length) { + if(header.isMasked()) { + byte[] mask = header.getMask(); + + for (int i = 0; i < count; i++) { + buffer[i] ^= mask[i % 4]; + } + } + break; + } + } + } + } + } + + /** + * This is used to determine if the collector has finished. If it + * is not finished the collector will be registered to listen for + * an I/O interrupt to read further bytes of the frame. + * + * @return true if the collector has finished consuming + */ + public boolean isFinished() { + if(header.isFinished()) { + int length = header.getLength(); + + if(count == length) { + return true; + } + } + return false; + } + + /** + * This resets the collector to its original state so that it can + * be reused. Reusing the collector has obvious benefits as it will + * reduce the amount of memory churn for the server. + */ + public void clear() { + header.clear(); + count = 0; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java new file mode 100644 index 0000000..1a99d30 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameEncoder.java @@ -0,0 +1,229 @@ +/* + * FrameEncoder.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.socket.FrameType.BINARY; +import static org.simpleframework.http.socket.FrameType.CLOSE; +import static org.simpleframework.http.socket.FrameType.TEXT; +import static org.simpleframework.http.socket.service.ServiceEvent.WRITE_FRAME; + +import java.io.IOException; + +import org.simpleframework.http.Request; +import org.simpleframework.http.socket.CloseCode; +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.FrameType; +import org.simpleframework.http.socket.Reason; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>FrameEncoder</code> is used to encode data as frames as + * defined by RFC 6455. This can encode binary, and text frames as + * well as control frames. All frames generated are written to the + * underlying channel but are not flushed so that multiple frames + * can be buffered before the final flush is made. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.FrameConnection + */ +class FrameEncoder { + + /** + * This is the underlying sender used to send the frames. + */ + private final OutputBarrier barrier; + + /** + * This is the TCP channel the frames are delivered over. + */ + private final Channel channel; + + /** + * This is used to trace the traffic on the channel. + */ + private final Trace trace; + + /** + * This is the charset used to encode the text frames with. + */ + private final String charset; + + /** + * Constructor for the <code>FrameEncoder</code> object. This is + * used to create an encoder to sending frames over the provided + * channel. Frames send remain unflushed so they can be batched + * on a single output buffer. + * + * @param request contains the opening handshake information + */ + public FrameEncoder(Request request) { + this(request, "UTF-8"); + } + + /** + * Constructor for the <code>FrameEncoder</code> object. This is + * used to create an encoder to sending frames over the provided + * channel. Frames send remain unflushed so they can be batched + * on a single output buffer. + * + * @param request contains the opening handshake information + * @param charset this is the character encoding to encode with + */ + public FrameEncoder(Request request, String charset) { + this.barrier = new OutputBarrier(request, 5000); + this.channel = request.getChannel(); + this.trace = channel.getTrace(); + this.charset = charset; + } + + /** + * This is used to encode the provided data as a WebSocket frame as + * of RFC 6455. The encoded data is written to the underlying socket + * and the number of bytes generated is returned. + * + * @param text this is the data used to encode the frame + * + * @return the size of the generated frame including the header + */ + public int encode(String text) throws IOException { + byte[] data = text.getBytes(charset); + return encode(TEXT, data, true); + } + + /** + * This is used to encode the provided data as a WebSocket frame as + * of RFC 6455. The encoded data is written to the underlying socket + * and the number of bytes generated is returned. + * + * @param data this is the data used to encode the frame + * + * @return the size of the generated frame including the header + */ + public int encode(byte[] data) throws IOException { + return encode(BINARY, data, true); + } + + /** + * This is used to encode the provided data as a WebSocket frame as + * of RFC 6455. The encoded data is written to the underlying socket + * and the number of bytes generated is returned. A close frame with + * a reason is similar to a text frame with the exception that the + * first two bytes of the frame payload contains the close code as + * a two byte integer in network byte order. The body of the close + * frame may contain UTF-8 encoded data with a reason, the + * interpretation of which is not defined by RFC 6455. + * + * @param reason this is the data used to encode the frame + * + * @return the size of the generated frame including the header + */ + public int encode(Reason reason) throws IOException { + CloseCode code = reason.getCode(); + String text = reason.getText(); + byte[] header = code.getData(); + + if(text != null) { + byte[] data = text.getBytes(charset); + byte[] message = new byte[data.length + 2]; + + message[0] = header[0]; + message[1] = header[1]; + + for(int i = 0; i < data.length; i++) { + message[i + 2] = data[i]; + } + return encode(CLOSE, message, true); + } + return encode(CLOSE, header, true); + } + + /** + * This is used to encode the provided frame as a WebSocket frame as + * of RFC 6455. The encoded data is written to the underlying socket + * and the number of bytes generated is returned. + * + * @param frame this is frame that is to be send over the channel + * + * @return the size of the generated frame including the header + */ + public int encode(Frame frame) throws IOException { + FrameType code = frame.getType(); + byte[] data = frame.getBinary(); + boolean last = frame.isFinal(); + + return encode(code, data, last); + } + + /** + * This is used to encode the provided frame as a WebSocket frame as + * of RFC 6455. The encoded data is written to the underlying socket + * and the number of bytes generated is returned. + * + * @param type this is the type of frame that is to be encoded + * @param data this is the data used to create the frame + * @param last determines if the is the last frame in a sequence + * + * @return the size of the generated frame including the header + */ + private int encode(FrameType type, byte[] data, boolean last) throws IOException { + byte[] header = new byte[10]; + long length = data.length; + int count = 0; + + if (last) { + header[0] |= 1 << 7; + } + header[0] |= type.code % 128; + + if (length <= 125) { + header[1] = (byte) length; + count = 2; + } else if (length >= 126 && length <= 65535) { + header[1] = (byte) 126; + header[2] = (byte) ((length >>> 8) & 0xff); + header[3] = (byte) (length & 0xff); + count = 4; + } else { + header[1] = (byte) 127; + header[2] = (byte) ((length >>> 56) & 0xff); + header[3] = (byte) ((length >>> 48) & 0xff); + header[4] = (byte) ((length >>> 40) & 0xff); + header[5] = (byte) ((length >>> 32) & 0xff); + header[6] = (byte) ((length >>> 24) & 0xff); + header[7] = (byte) ((length >>> 16) & 0xff); + header[8] = (byte) ((length >>> 8) & 0xff); + header[9] = (byte) (length & 0xff); + count = 10; + } + byte[] reply = new byte[count + data.length]; + + for (int i = 0; i < count; i++) { + reply[i] = header[i]; + } + for (int i = 0; i < length; i++) { + reply[i + count] = data[i]; + } + trace.trace(WRITE_FRAME, type); + barrier.send(reply); + + return reply.length; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeader.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeader.java new file mode 100644 index 0000000..a246451 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeader.java @@ -0,0 +1,80 @@ +/* + * FrameHeader.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import org.simpleframework.http.socket.FrameType; + +/** + * The <code>FrameHeader</code> represents the variable length header + * used for a WebSocket frame. It is used to determine the number of + * bytes that need to be consumed to successfully process a frame + * from the connected client. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.FrameConsumer + */ +interface FrameHeader { + + /** + * This is used to determine the type of frame. Interpretation of + * this type is outlined in RFC 6455 and can be loosely categorised + * as control frames and either data or binary frames. + * + * @return this returns the type of frame that this represents + */ + FrameType getType(); + + /** + * This provides the client mask send with the request. The mask is + * a 32 bit value that is used as an XOR bitmask of the client + * payload. Masking applies only in the client to server direction. + * + * @return this returns the 32 bit mask used for this frame + */ + byte[] getMask(); + + /** + * This provides the length of the payload within the frame. It + * is used to determine how much data to consume from the underlying + * TCP stream in order to recreate the frame to dispatch. + * + * @return the number of bytes used in the frame + */ + int getLength(); + + /** + * This is used to determine if the frame is masked. All client + * frames should be masked according to RFC 6455. If masked the + * payload will have its contents bitmasked with a 32 bit value. + * + * @return this returns true if the payload has been masked + */ + boolean isMasked(); + + /** + * This is used to determine if the frame is the final frame in + * a sequence of fragments or a whole frame. If this returns false + * then the frame is a continuation from from a sequence of + * fragments, otherwise it is a whole frame or the last fragment. + * + * @return this returns false if the frame is a fragment + */ + boolean isFinal(); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeaderConsumer.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeaderConsumer.java new file mode 100644 index 0000000..d651ea9 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameHeaderConsumer.java @@ -0,0 +1,235 @@ +/* + * FrameHeaderConsumer.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import java.io.IOException; + +import org.simpleframework.http.socket.FrameType; +import org.simpleframework.transport.ByteCursor; + +/** + * The <code>FrameHeaderConsumer</code> is used to consume frames from + * a connected TCP channel. This is a state machine that can consume + * the data one byte at a time until the entire header has been consumed. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.FrameConsumer + */ +class FrameHeaderConsumer implements FrameHeader { + + /** + * This is the frame type which represents the opcode. + */ + private FrameType type; + + /** + * If header consumed was from a client frame the data is masked. + */ + private boolean masked; + + /** + * Determines if this frame is part of a larger sequence. + */ + private boolean last; + + /** + * This is the mask that is used to obfuscate client frames. + */ + private byte[] mask; + + /** + * This is the octet that is used to read one byte at a time. + */ + private byte[] octet; + + /** + * Required number of bytes within the frame header. + */ + private int required; + + /** + * This represents the length of the frame payload. + */ + private int length; + + /** + * This determines the count of the mask bytes read. + */ + private int count; + + /** + * Constructor for the <code>FrameHeaderConsumer</code> object. This + * is used to create a consumer to read the bytes that form the + * frame header from an underlying TCP connection. + */ + public FrameHeaderConsumer() { + this.octet = new byte[1]; + this.mask = new byte[4]; + this.length = -1; + } + + /** + * This provides the length of the payload within the frame. It + * is used to determine how much data to consume from the underlying + * TCP stream in order to recreate the frame to dispatch. + * + * @return the number of bytes used in the frame + */ + public int getLength() { + return length; + } + + /** + * This provides the client mask send with the request. The mask is + * a 32 bit value that is used as an XOR bitmask of the client + * payload. Masking applies only in the client to server direction. + * + * @return this returns the 32 bit mask used for this frame + */ + public byte[] getMask() { + return mask; + } + + /** + * This is used to determine the type of frame. Interpretation of + * this type is outlined in RFC 6455 and can be loosely categorised + * as control frames and either data or binary frames. + * + * @return this returns the type of frame that this represents + */ + public FrameType getType() { + return type; + } + + /** + * This is used to determine if the frame is masked. All client + * frames should be masked according to RFC 6455. If masked the + * payload will have its contents bitmasked with a 32 bit value. + * + * @return this returns true if the payload has been masked + */ + public boolean isMasked() { + return masked; + } + + /** + * This is used to determine if the frame is the final frame in + * a sequence of fragments or a whole frame. If this returns false + * then the frame is a continuation from from a sequence of + * fragments, otherwise it is a whole frame or the last fragment. + * + * @return this returns false if the frame is a fragment + */ + public boolean isFinal() { + return last; + } + + /** + * This consumes frame bytes using the provided cursor. The consumer + * acts as a state machine by consuming the data as that data + * becomes available, this allows it to consume data asynchronously + * and dispatch once the whole frame has been consumed. + * + * @param cursor the cursor to consume the frame data from + */ + public void consume(ByteCursor cursor) throws IOException { + if (cursor.isReady()) { + if (type == null) { + int count = cursor.read(octet); + + if (count <= 0) { + throw new IOException("Ready cursor produced no data"); + } + type = FrameType.resolveType(octet[0] & 0x0f); + + if(type == null) { + throw new IOException("Frame type code not supported"); + } + last = (octet[0] & 0x80) != 0; + } else { + if (length < 0) { + int count = cursor.read(octet); + + if (count <= 0) { + throw new IOException("Ready cursor produced no data"); + } + masked = (octet[0] & 0x80) != 0; + length = (octet[0] & 0x7F); + + if (length == 0x7F) { // 8 byte extended payload length + required = 8; + length = 0; + } else if (length == 0x7E) { // 2 bytes extended payload length + required = 2; + length = 0; + } + } else if (required > 0) { + int count = cursor.read(octet); + + if (count == -1) { + throw new IOException("Could not read length"); + } + length |= (octet[0] & 0xFF) << (8 * --required); + } else { + if (masked && count < mask.length) { + int size = cursor.read(mask, count, mask.length - count); + + if (size == -1) { + throw new IOException("Could not read mask"); + } + count += size; + } + } + } + } + } + + /** + * This is used to determine if the collector has finished. If it + * is not finished the collector will be registered to listen for + * an I/O intrrupt to read further bytes of the frame. + * + * @return true if the collector has finished consuming + */ + public boolean isFinished() { + if(type != null) { + if(length >= 0 && required == 0) { + if(masked) { + return count == mask.length; + } + return true; + } + } + return false; + } + + /** + * This resets the collector to its original state so that it can + * be reused. Reusing the collector has obvious benefits as it will + * reduce the amount of memory churn for the server. + */ + public void clear() { + type = null; + length = -1; + required = 0; + masked = false; + count = 0; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameProcessor.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameProcessor.java new file mode 100644 index 0000000..c7528d4 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/FrameProcessor.java @@ -0,0 +1,255 @@ +/* + * FrameProcessor.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.socket.CloseCode.NORMAL_CLOSURE; +import static org.simpleframework.http.socket.service.ServiceEvent.ERROR; +import static org.simpleframework.http.socket.service.ServiceEvent.READ_FRAME; +import static org.simpleframework.http.socket.service.ServiceEvent.READ_PING; +import static org.simpleframework.http.socket.service.ServiceEvent.READ_PONG; +import static org.simpleframework.http.socket.service.ServiceEvent.WRITE_PONG; + +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.simpleframework.http.Request; +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.FrameListener; +import org.simpleframework.http.socket.FrameType; +import org.simpleframework.http.socket.Reason; +import org.simpleframework.http.socket.Session; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteCursor; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>FrameProcessor</code> object is used to process incoming + * data and dispatch that data as WebSocket frames. Dispatching of the + * frames is done by making a callback to <code>FrameListener</code> + * objects registered. In addition to frames this will also notify of + * any errors that occur or on connection closure. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.FrameConsumer + */ +class FrameProcessor { + + /** + * This is the set of listeners to dispatch frames to. + */ + private final Set<FrameListener> listeners; + + /** + * This is used to extract the reason description from a frame. + */ + private final ReasonExtractor extractor; + + /** + * This is used to consume the frames from the underling channel. + */ + private final FrameConsumer consumer; + + /** + * This is the encoder that is used to send control messages. + */ + private final FrameEncoder encoder; + + /** + * This is used to determine if a close notification was sent. + */ + private final AtomicBoolean closed; + + /** + * This is the cursor used to maintain a read seek position. + */ + private final ByteCursor cursor; + + /** + * This is the session associated with the WebSocket connection. + */ + private final Session session; + + /** + * This is the underlying TCP channel this reads frames from. + */ + private final Channel channel; + + /** + * This is the reason message used for a normal closure. + */ + private final Reason normal; + + /** + * This is used to trace the events that occur on the channel. + */ + private final Trace trace; + + /** + * Constructor for the <code>FrameProcessor</code> object. This is + * used to create a processor that can consume and dispatch frames + * as defined by RFC 6455 to a set of registered listeners. + * + * @param encoder this is the encoder used to send control frames + * @param session this is the session associated with the channel + * @param channel this is the channel to read frames from + */ + public FrameProcessor(FrameEncoder encoder, Session session, Request request) { + this.listeners = new CopyOnWriteArraySet<FrameListener>(); + this.normal = new Reason(NORMAL_CLOSURE); + this.extractor = new ReasonExtractor(); + this.consumer = new FrameConsumer(); + this.closed = new AtomicBoolean(); + this.channel = request.getChannel(); + this.cursor = channel.getCursor(); + this.trace = channel.getTrace(); + this.encoder = encoder; + this.session = session; + } + + /** + * This is used to register a <code>FrameListener</code> to this + * instance. The registered listener will receive all user frames + * and control frames sent from the client. Also, when the frame + * is closed or when an unexpected error occurs the listener is + * notified. Any number of listeners can be registered at any time. + * + * @param listener this is the listener that is to be registered + */ + public void register(FrameListener listener) { + listeners.add(listener); + } + + /** + * This is used to remove a <code>FrameListener</code> from this + * instance. After removal the listener will no longer receive + * any user frames or control messages from this specific instance. + * + * @param listener this is the listener to be removed + */ + public void remove(FrameListener listener) { + listeners.remove(listener); + } + + /** + * This is used to process frames consumed from the underlying TCP + * connection. It will respond to control frames such as pings and + * will also handle close frames. Each frame, regardless of its + * type will be dispatched to any <code>FrameListener</code> objects + * that are registered with the processor. If an a close frame is + * received it will echo that close frame, with the same close code + * and back to the sender as suggested by RFC 6455 section 5.5.1. + */ + public void process() throws IOException { + if(cursor.isReady()) { + consumer.consume(cursor); + + if(consumer.isFinished()) { + Frame frame = consumer.getFrame(); + FrameType type = frame.getType(); + + trace.trace(READ_FRAME, type); + + if(type.isPong()) { + trace.trace(READ_PONG); + } + if(type.isPing()){ + Frame response = frame.getFrame(FrameType.PONG); + + trace.trace(READ_PING); + encoder.encode(response); + trace.trace(WRITE_PONG); + } + for(FrameListener listener : listeners) { + listener.onFrame(session, frame); + } + if(type.isClose()){ + Reason reason = extractor.extract(frame); + + if(reason != null) { + close(reason); + } else { + close(); + } + } + consumer.clear(); + } + } + } + + /** + * This is used to report failures back to the client. Any I/O + * or frame processing exception is reported back to all of the + * registered listeners so that they can take action. The + * underlying TCP connection is closed after any failure. + * + * @param reason this is the cause of the failure + */ + public void failure(Exception reason) throws IOException { + if(!closed.getAndSet(true)) { + for(FrameListener listener : listeners) { + try { + listener.onError(session, reason); + } catch(Exception cause) { + trace.trace(ERROR, cause); + } + } + } + } + + /** + * This is used to close the connection without a specific reason. + * The close reason will be sent as a control frame before the + * TCP connection is terminated. All registered listeners will be + * notified of the close event. + * + * @param reason this is the reason for the connection closure + */ + public void close(Reason reason) throws IOException{ + if(!closed.getAndSet(true)) { + for(FrameListener listener : listeners) { + try { + listener.onClose(session, reason); + } catch(Exception cause) { + trace.trace(ERROR, cause); + } + } + } + } + + /** + * This is used to close the connection when it has not responded + * to any activity for a configured period of time. It may be + * possible to send up a control frame, however if the TCP channel + * is closed this will just notify the listeners. + */ + public void close() throws IOException{ + if(!closed.getAndSet(true)) { + try { + for(FrameListener listener : listeners) { + listener.onClose(session, normal); + } + } catch(Exception cause) { + trace.trace(ERROR, cause); + } + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/OutputBarrier.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/OutputBarrier.java new file mode 100644 index 0000000..3da2635 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/OutputBarrier.java @@ -0,0 +1,99 @@ +/* + * OutputBarrier.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.io.IOException; +import java.util.concurrent.locks.ReentrantLock; + +import org.simpleframework.http.Request; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteWriter; + +/** + * The <code>OutputBarrier</code> is used to ensure that control + * frames and data frames do not get sent at the same time. Sending + * both at the same time could lead to the status checking thread + * being blocked and this could eventually exhaust the thread pool. + * + * @author Niall Gallagher + */ +class OutputBarrier { + + /** + * This is used to check if there is an operation in progress. + */ + private final ReentrantLock lock; + + /** + * This is the underlying sender used to send the frames. + */ + private final ByteWriter writer; + + /** + * This is the TCP channel the frames are delivered over. + */ + private final Channel channel; + + /** + * This is the length of time to wait before failing to lock. + */ + private final long duration; + + /** + * Constructor for the <code>OutputBarrier</code> object. This + * is used to ensure that if there is currently a blocking write + * in place that the <code>SessionChecker</code> will not end up + * being blocked if it attempts to send a control frame. + * + * @param request this is the request to get the TCP channel from + * @param duration this is the length of time to wait for the lock + */ + public OutputBarrier(Request request, long duration) { + this.lock = new ReentrantLock(); + this.channel = request.getChannel(); + this.writer = channel.getWriter(); + this.duration = duration; + } + + /** + * This method is used to send all frames. It is important that + * a lock is used to protect this so that if there is an attempt + * to send out a control frame while the connection is blocked + * there is an exception thrown. + * + * @param frame this is the frame to send over the TCP channel + */ + public void send(byte[] frame) throws IOException { + try { + if(!lock.tryLock(duration, MILLISECONDS)) { + throw new IOException("Transport lock could not be acquired"); + } + try { + writer.write(frame); + writer.flush(); // less throughput, better latency + } finally { + lock.unlock(); + } + } catch(Exception e) { + throw new IOException("Error writing to transport", e); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/PathRouter.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/PathRouter.java new file mode 100644 index 0000000..9deb66a --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/PathRouter.java @@ -0,0 +1,111 @@ +/* + * PathRouter.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_PROTOCOL; +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_VERSION; +import static org.simpleframework.http.Protocol.UPGRADE; +import static org.simpleframework.http.Protocol.WEBSOCKET; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.simpleframework.http.Path; +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +/** + * The <code>PathRouter</code> is used when there are multiple + * services that can be used. Each service is selected based on the + * path sent in the initiating request. If a match cannot be made + * based on the request then a default service us chosen. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.RouterContainer + */ +public class PathRouter implements Router { + + /** + * This is the set of services that can be selected. + */ + private final Map<String, Service> registry; + + /** + * This is the default service chosen if there is no match. + */ + private final Service primary; + + /** + * Constructor for the <code>PathRouter</code> object. This is used + * to create a router using a selection of services that can be + * selected using the path provided in the initiating request. + * + * @param registry this is the registry of available services + * @param primary this is the default service to use + */ + public PathRouter(Map<String, Service> registry, Service primary) throws IOException { + this.registry = registry; + this.primary = primary; + } + + /** + * This is used to route an incoming request to a service if + * the request represents a WebSocket handshake as defined by + * RFC 6455. If the request is not a session initiating handshake + * then this will return a null value to allow it to be processed + * by some other part of the server. + * + * @param request this is the request to use for routing + * @param response this is the response to establish the session + * + * @return a service that can be used to process the session + */ + public Service route(Request request, Response response) { + String token = request.getValue(UPGRADE); + + if(token != null) { + if(token.equalsIgnoreCase(WEBSOCKET)) { + List<String> protocols = request.getValues(SEC_WEBSOCKET_PROTOCOL); + String version = request.getValue(SEC_WEBSOCKET_VERSION); + Path path = request.getPath(); + String normal = path.getPath(); + + if(version != null) { + response.setValue(SEC_WEBSOCKET_VERSION, version); + } + for(String protocol : protocols) { + String original = response.getValue(SEC_WEBSOCKET_PROTOCOL); + + if(original == null) { + response.setValue(SEC_WEBSOCKET_PROTOCOL, protocol); + } + } + Service service = registry.get(normal); + + if(service != null) { + return service; + } + return primary; + } + } + return null; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ProtocolRouter.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ProtocolRouter.java new file mode 100644 index 0000000..54060c9 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ProtocolRouter.java @@ -0,0 +1,105 @@ +/* + * ProtocolRouter.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_PROTOCOL; +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_VERSION; +import static org.simpleframework.http.Protocol.UPGRADE; +import static org.simpleframework.http.Protocol.WEBSOCKET; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +/** + * The <code>ProtocolRouter</code> is used when there are multiple + * services that can be used. Each service is selected based on the + * protocol sent in the initiating request. If a match cannot be + * made based on the request then a default service us chosen. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.RouterContainer + */ +public class ProtocolRouter implements Router { + + /** + * This is the set of services that can be selected. + */ + private final Map<String, Service> registry; + + /** + * This is the default service chosen if there is no match. + */ + private final Service primary; + + /** + * Constructor for the <code>ProtocolRouter</code> object. This is + * used to create a router using a selection of services that can + * be selected using the <code>Sec-WebSocket-Protocol</code> header + * sent in the initiating request by the client. + * + * @param registry this is the registry of available services + * @param primary this is the default service to use + */ + public ProtocolRouter(Map<String, Service> registry, Service primary) throws IOException { + this.registry = registry; + this.primary = primary; + } + + /** + * This is used to route an incoming request to a service if + * the request represents a WebSocket handshake as defined by + * RFC 6455. If the request is not a session initiating handshake + * then this will return a null value to allow it to be processed + * by some other part of the server. + * + * @param request this is the request to use for routing + * @param response this is the response to establish the session + * + * @return a service that can be used to process the session + */ + public Service route(Request request, Response response) { + String token = request.getValue(UPGRADE); + + if(token != null) { + if(token.equalsIgnoreCase(WEBSOCKET)) { + List<String> protocols = request.getValues(SEC_WEBSOCKET_PROTOCOL); + String version = request.getValue(SEC_WEBSOCKET_VERSION); + + if(version != null) { + response.setValue(SEC_WEBSOCKET_VERSION, version); + } + for(String protocol : protocols) { + Service service = registry.get(protocol); + + if(service != null) { + response.setValue(SEC_WEBSOCKET_PROTOCOL, protocol); + return service; + } + } + return primary; + } + } + return null; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ReasonExtractor.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ReasonExtractor.java new file mode 100644 index 0000000..fb6ce88 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ReasonExtractor.java @@ -0,0 +1,114 @@ +/* + * ReasonExtractor.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.socket.CloseCode.NO_STATUS_CODE; + +import org.simpleframework.http.socket.CloseCode; +import org.simpleframework.http.socket.DataConverter; +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.Reason; + +/** + * The <code>ReasonExtractor</code> object is used to extract the close + * reason from a frame payload. If their is no close reason this will + * return a <code>Reason</code> with just the close code. Finally in + * the event of a botched frame being sent with no close code then the + * close code 1005 is used to indicate no reason. + * + * @author Niall Gallagher + */ +class ReasonExtractor { + + /** + * This is the data converter object used to convert data. + */ + private final DataConverter converter; + + /** + * Constructor for the <code>ReasonExtractor</code> object. This + * is used to create an extractor for close code and the close + * reason descriptions. All descriptions are decoded using the + * UTF-8 character encoding. + */ + public ReasonExtractor() { + this.converter = new DataConverter(); + } + + /** + * This is used to extract a reason from the provided frame. The + * close reason is taken from the first two bytes of the frame + * payload and the UTF-8 string that follows is the description. + * + * @param frame this is the frame to extract the reason from + * + * @return a reason containing the close code and reason + */ + public Reason extract(Frame frame) { + byte[] data = frame.getBinary(); + + if(data.length > 0) { + CloseCode code = extractCode(data); + String text = extractText(data); + + return new Reason(code, text); + } + return new Reason(NO_STATUS_CODE); + } + + /** + * This method is used to extract the UTF-8 description from the + * frame payload. If there are only two bytes within the payload + * then this will return null for the reason. + * + * @param data the frame payload to extract the description from + * + * @return returns the description within the payload + */ + private String extractText(byte[] data) { + int length = data.length - 2; + + if(length > 0) { + return converter.convert(data, 2, length); + } + return null; + } + + /** + * This method is used to extract the close code. The close code + * is an two byte integer in network byte order at the start + * of the close frame payload. This code is required by RFC 6455 + * however if not code is available code 1005 is returned. + * + * @param data the frame payload to extract the description from + * + * @return returns the description within the payload + */ + private CloseCode extractCode(byte[] data) { + int length = data.length; + + if(length > 0) { + int high = data[0]; + int low = data[1]; + + return CloseCode.resolveCode(high, low); + } + return NO_STATUS_CODE; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RequestValidator.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RequestValidator.java new file mode 100644 index 0000000..7e47dc3 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RequestValidator.java @@ -0,0 +1,137 @@ +/* + * RequestValidator.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.Protocol.CONNECTION; +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_KEY; +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_VERSION; +import static org.simpleframework.http.Protocol.UPGRADE; +import static org.simpleframework.http.Protocol.WEBSOCKET; + +import java.util.List; + +import org.simpleframework.http.Request; + +/** + * The <code>RequestValidator</code> object is used to ensure requests + * for confirm to RFC 6455 section 4.2.1. The client opening handshake + * must consist of several parts, including a version of 13 referring + * to RFC 6455, a WebSocket key, and the required HTTP connection + * details. If any of these are missing the server is obliged to + * respond with a HTTP 400 response indicating a bad request. + * + * @author Niall Gallagher + */ +class RequestValidator { + + /** + * This is the request forming the client part of the handshake. + */ + private final Request request; + + /** + * This is the version referring to the required client version. + */ + private final String version; + + /** + * Constructor for the <code>RequestValidator</code> object. This + * is used to create a plain vanilla validator that uses version + * 13 as dictated by RFC 6455 section 4.2.1. + * + * @param request this is the handshake request from the client + */ + public RequestValidator(Request request) { + this(request, "13"); + } + + /** + * Constructor for the <code>RequestValidator</code> object. This + * is used to create a plain vanilla validator that uses version + * 13 as dictated by RFC 6455 section 4.2.1. + * + * @param request this is the handshake request from the client + * @param version a version other than 13 if desired + */ + public RequestValidator(Request request, String version) { + this.request = request; + this.version = version; + } + + /** + * This is used to determine if the client handshake request had + * all the required headers as dictated by RFC 6455 section 4.2.1. + * If the request does not contain any of these parts then this + * will return false, indicating a HTTP 400 response should be + * sent to the client. + * + * @return true if the request was a valid handshake + */ + public boolean isValid() { + if(!isProtocol()) { + return false; + } + if(!isUpgrade()) { + return false; + } + return true; + } + + /** + * This is used to determine if the request is a valid WebSocket + * handshake of the correct version. This also checks to see if + * the request contained the required handshake token. + * + * @return this returns true if the request is a valid handshake + */ + private boolean isProtocol() { + String protocol = request.getValue(SEC_WEBSOCKET_VERSION); + String token = request.getValue(SEC_WEBSOCKET_KEY); + + if(token != null) { + return version.equals(protocol); + } + return false; + } + + /** + * Here we check to ensure that there is a HTTP connection header + * with the required upgrade token. The upgrade token may be + * one of many, so all must be checked. Finally to ensure that + * the upgrade is for a WebSocket the upgrade header is checked. + * + * @return this returns true if the request is an upgrade + */ + private boolean isUpgrade() { + List<String> tokens = request.getValues(CONNECTION); + + for(String token : tokens) { + if(token.equalsIgnoreCase(UPGRADE)) { + String upgrade = request.getValue(UPGRADE); + + if(upgrade != null) { + return upgrade.equalsIgnoreCase(WEBSOCKET); + } + return false; + } + } + return false; + } + +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ResponseBuilder.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ResponseBuilder.java new file mode 100644 index 0000000..0ba780e --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ResponseBuilder.java @@ -0,0 +1,159 @@ +/* + * ResponseBuilder.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.Protocol.CLOSE; +import static org.simpleframework.http.Protocol.CONNECTION; +import static org.simpleframework.http.Protocol.DATE; +import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_ACCEPT; +import static org.simpleframework.http.Protocol.UPGRADE; +import static org.simpleframework.http.Protocol.WEBSOCKET; +import static org.simpleframework.http.socket.service.ServiceEvent.WRITE_HEADER; + +import java.io.IOException; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.http.Status; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.ByteWriter; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>ResponseBuilder</code> object is used to build a response + * to a WebSocket handshake. In order for a successful handshake to + * complete a HTTP request must have a version of 13 referring + * to RFC 6455, a WebSocket key, and the required HTTP connection + * details. If any of these are missing the server is obliged to + * respond with a HTTP 400 response indicating a bad request. + * + * @author Niall Gallagher + */ +class ResponseBuilder { + + /** + * This is used to validate the initiating WebSocket request. + */ + private final RequestValidator validator; + + /** + * This is the accept token generated for the request. + */ + private final AcceptToken token; + + /** + * This is the sender used to send the WebSocket response. + */ + private final ByteWriter writer; + + /** + * This is the response to the WebSocket handshake. + */ + private final Response response; + + /** + * This is the underlying TCP channel for the request. + */ + private final Channel channel; + + /** + * This is used to trace the activity for the handshake. + */ + private final Trace trace; + + /** + * Constructor for the <code>ResponseBuilder</code> object. In order + * to process the WebSocket handshake this requires the original + * request and the response as well as the underlying TCP channel + * which forms the basis of the WebSocket connection. + * + * @param request this is the request that initiated the handshake + * @param response this is the response for the handshake + */ + public ResponseBuilder(Request request, Response response) throws Exception { + this.validator = new RequestValidator(request); + this.token = new AcceptToken(request); + this.channel = request.getChannel(); + this.writer = channel.getWriter(); + this.trace = channel.getTrace(); + this.response = response; + } + + /** + * This is used to determine if the client handshake request had + * all the required headers as dictated by RFC 6455 section 4.2.1. + * If the request does not contain any of these parts then this + * will return false, indicating a HTTP 400 response is sent to + * the client, otherwise a HTTP 101 response is sent. + */ + public void commit() throws IOException { + if(validator.isValid()) { + accept(); + } else { + reject(); + } + } + + /** + * This is used to respond to the client with a HTTP 400 response + * indicating the WebSocket handshake failed. No response body is + * sent with the rejection message and the underlying TCP channel + * is closed to prevent further use of the connection. + */ + private void reject() throws IOException { + long time = System.currentTimeMillis(); + + response.setStatus(Status.BAD_REQUEST); + response.setValue(CONNECTION, CLOSE); + response.setDate(DATE, time); + + String header = response.toString(); + byte[] message = header.getBytes("UTF-8"); + + trace.trace(WRITE_HEADER, header); + writer.write(message); + writer.flush(); + writer.close(); + } + + /** + * This is used to respond to the client with a HTTP 101 response + * to indicate that the WebSocket handshake succeeeded. Once this + * response has been sent all traffic between the client and + * server will be with WebSocket frames as defined by RFC 6455. + */ + private void accept() throws IOException { + long time = System.currentTimeMillis(); + String accept = token.create(); + + response.setStatus(Status.SWITCHING_PROTOCOLS); + response.setDescription(UPGRADE); + response.setValue(CONNECTION, UPGRADE); + response.setDate(DATE, time); + response.setValue(SEC_WEBSOCKET_ACCEPT, accept); + response.setValue(UPGRADE, WEBSOCKET); + + String header = response.toString(); + byte[] message = header.getBytes("UTF-8"); + + trace.trace(WRITE_HEADER, header); + writer.write(message); + writer.flush(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Router.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Router.java new file mode 100644 index 0000000..3b466f5 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Router.java @@ -0,0 +1,59 @@ +/* + * Router.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +/** + * The <code>Router</code> interface represents a means of routing + * a session initiating request to the correct service. Typically + * a service is chosen based on the sub-protocol provided in the + * initiating request, however it can be chosen on any criteria + * available in the request. An initiating request must contain + * a <code>Connection</code> header with the <code>websocket</code> + * token according to RFC 6455 section 4.2.1. If the request does + * not contain this token it is treated as a normal request and + * a <code>Service</code> will not be resolved. + * <p> + * If a service has been successfully chosen from the initiating + * request the the value of <code>Sec-WebSocket-Protocol</code> will + * contain either the chosen protocol if a match was made with the + * initiating request or null to indicate a default choice. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.RouterContainer + */ +public interface Router { + + /** + * This is used to route an incoming request to a service if + * the request represents a WebSocket handshake as defined by + * RFC 6455. If the request is not a session initiating handshake + * then this must return a null value to allow it to be processed + * by some other part of the server. + * + * @param request this is the request to use for routing + * @param response this is the response to establish the session + * + * @return a service that can be used to process the session + */ + Service route(Request request, Response response); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RouterContainer.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RouterContainer.java new file mode 100644 index 0000000..3b018a9 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/RouterContainer.java @@ -0,0 +1,109 @@ +/* + * RouterContainer.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import java.io.IOException; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.http.core.Container; + +/** + * The <code>RouterContainer</code> is used to route requests that + * satisfy a WebSocket opening handshake to a specific service. Each + * request intercepted by this <code>Container</code> implementation + * is examined for opening handshake criteria as specified by RFC 6455, + * and if it contains the required information it is router to a + * specific service using a <code>Router</code> implementation. If the + * request does not contain the required criteria it is handled by + * an internal container delegate. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.service.Router + */ +public class RouterContainer implements Container { + + /** + * This is the service dispatcher used to dispatch requests. + */ + private final ServiceDispatcher dispatcher; + + /** + * This is the container used to handle traditional requests. + */ + private final Container container; + + /** + * This is the router used to select specific services. + */ + private final Router router; + + /** + * Constructor for the <code>RouterContainer</code> object. This + * requires a container to delegate traditional requests to and + * a <code>Router</code> implementation which can be used to + * select a service to dispatch a WebSocket session to. + * + * @param container this is the container to delegate to + * @param router this is the router used to select services + * @param threads this contains the number of threads to use + */ + public RouterContainer(Container container, Router router, int threads) throws IOException { + this(container, router, threads, 10000); + } + + /** + * Constructor for the <code>RouterContainer</code> object. This + * requires a container to delegate traditional requests to and + * a <code>Router</code> implementation which can be used to + * select a service to dispatch a WebSocket session to. + * + * @param container this is the container to delegate to + * @param router this is the router used to select services + * @param threads this contains the number of threads to use + * @param ping this is the frequency to send ping frames with + */ + public RouterContainer(Container container, Router router, int threads, long ping) throws IOException { + this.dispatcher = new ServiceDispatcher(router, threads, ping); + this.container = container; + this.router = router; + } + + /** + * This method is used to create a dispatch a <code>Session</code> to + * a specific service selected by a router. If the session initiating + * handshake fails for any reason this will close the underlying TCP + * connection and send a HTTP 400 response back to the client. All + * traditional requests that do not represent an WebSocket opening + * handshake are dispatched to the internal container. + * + * @param req the request that contains the client HTTP message + * @param resp the response used to deliver the server response + */ + public void handle(Request req, Response resp) { + Service service = router.route(req, resp); + + if(service != null) { + dispatcher.dispatch(req, resp); + } else { + container.handle(req, resp); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Service.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Service.java new file mode 100644 index 0000000..d95c01f --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/Service.java @@ -0,0 +1,44 @@ +/* + * Service.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import org.simpleframework.http.socket.Session; + +/** + * The <code>Service</code> interface represents a service that can be + * used to communicate with the WebSocket protocol defined in RFC 6455. + * Typically a service will implement a sub-protocol negotiated from + * the initiating HTTP request. The service should be considered a + * hand off point rather than an place to implement business logic. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.FrameChannel + */ +public interface Service { + + /** + * This method connects a new session with a service implementation. + * Connecting a session with a service in this way should not block + * as it could cause starvation of the servicing thread pool. + * + * @param session the new session to connect to the service + */ + void connect(Session session); +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceChannel.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceChannel.java new file mode 100644 index 0000000..ad5325c --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceChannel.java @@ -0,0 +1,149 @@ +/* + * ServiceChannel.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import java.io.IOException; + +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.FrameListener; +import org.simpleframework.http.socket.Reason; +import org.simpleframework.http.socket.FrameChannel; + +/** + * The <code>ServiceChannel</code> represents a full duplex communication + * channel as defined by RFC 6455. Any instance of this will provide + * a means to perform asynchronous writes and reads to a remote client + * using a lightweight framing protocol. A frame is a finite length + * sequence of bytes that can hold either text or binary data. Also, + * control frames are used to perform heartbeat monitoring and closure. + * <p> + * For convenience frames can be consumed from the socket via a + * callback to a registered listener. This avoids having to poll each + * socket for data and provides a asynchronous event driven model of + * communication, which greatly reduces overhead and complication. + * + * @author Niall Gallagher + */ +class ServiceChannel implements FrameChannel { + + /** + * This is the internal channel for full duplex communication. + */ + private final FrameChannel channel; + + /** + * Constructor for the <code>ServiceChannel</code> object. This is + * used to create a channel that is given to the application. This + * is synchronized so only one frame can be dispatched at a time. + * + * @param channel this is the channel to delegate to + */ + public ServiceChannel(FrameChannel channel) { + this.channel = channel; + } + + /** + * This is used to send data to the connected client. To prevent + * an application code from causing resource issues this will block + * as soon as a configured linked list of mapped memory buffers has + * been exhausted. Caution should be taken when writing a broadcast + * implementation that can write to multiple sockets as a badly + * behaving socket that has filled its output buffering capacity + * can cause congestion. + * + * @param data this is the data that is to be sent + */ + public synchronized void send(byte[] data) throws IOException { + channel.send(data); + } + + /** + * This is used to send text to the connected client. To prevent + * an application code from causing resource issues this will block + * as soon as a configured linked list of mapped memory buffers has + * been exhausted. Caution should be taken when writing a broadcast + * implementation that can write to multiple sockets as a badly + * behaving socket that has filled its output buffering capacity + * can cause congestion. + * + * @param text this is the text that is to be sent + */ + public synchronized void send(String text) throws IOException { + channel.send(text); + } + + /** + * This is used to send data to the connected client. To prevent + * an application code from causing resource issues this will block + * as soon as a configured linked list of mapped memory buffers has + * been exhausted. Caution should be taken when writing a broadcast + * implementation that can write to multiple sockets as a badly + * behaving socket that has filled its output buffering capacity + * can cause congestion. + * + * @param frame this is the frame that is to be sent + */ + public synchronized void send(Frame frame) throws IOException { + channel.send(frame); + } + + /** + * This is used to register a <code>FrameListener</code> to this + * instance. The registered listener will receive all user frames + * and control frames sent from the client. Also, when the frame + * is closed or when an unexpected error occurs the listener is + * notified. Any number of listeners can be registered at any time. + * + * @param listener this is the listener that is to be registered + */ + public synchronized void register(FrameListener listener) throws IOException { + channel.register(listener); + } + + /** + * This is used to remove a <code>FrameListener</code> from this + * instance. After removal the listener will no longer receive + * any user frames or control messages from this specific instance. + * + * @param listener this is the listener to be removed + */ + public synchronized void remove(FrameListener listener) throws IOException { + channel.remove(listener); + } + + /** + * This is used to close the connection with a specific reason. + * The close reason will be sent as a control frame before the + * TCP connection is terminated. + * + * @param reason the reason for closing the connection + */ + public synchronized void close(Reason reason) throws IOException { + channel.close(reason); + } + + /** + * This is used to close the connection without a specific reason. + * The close reason will be sent as a control frame before the + * TCP connection is terminated. + */ + public void close() throws IOException { + channel.close(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceDispatcher.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceDispatcher.java new file mode 100644 index 0000000..509495f --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceDispatcher.java @@ -0,0 +1,101 @@ +/* + * ServiceDispatcher.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import java.io.IOException; + +import org.simpleframework.common.thread.ConcurrentScheduler; +import org.simpleframework.common.thread.Scheduler; +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.transport.reactor.ExecutorReactor; +import org.simpleframework.transport.reactor.Reactor; + +/** + * The <code>ServiceDispatcher</code> object is used to perform the + * opening handshake for a WebSocket session. Once the session has been + * established it is connected to a <code>Service</code> where frames + * can be sent and received. If for any reason the handshake fails + * this will terminated the connection with a HTTP 400 response. + * + * @author Niall Gallagher + */ +class ServiceDispatcher { + + /** + * This is the session dispatcher used to dispatch the session. + */ + private final SessionDispatcher dispatcher; + + /** + * This is used to build the sessions from the handshake request. + */ + private final SessionBuilder builder; + + /** + * This is used asynchronously read frames from the TCP channel. + */ + private final Scheduler scheduler; + + /** + * This is used to notify of read events on the TCP channel. + */ + private final Reactor reactor; + + /** + * Constructor for the <code>ServiceDispatcher</code> object. The + * dispatcher created will dispatch WebSocket sessions to a service + * using the provided <code>Router</code> instance. + * + * @param router this is the router used to select a service + * @param threads this is the number of threads to use + */ + public ServiceDispatcher(Router router, int threads) throws IOException { + this(router, threads, 10000); + } + + /** + * Constructor for the <code>ServiceDispatcher</code> object. The + * dispatcher created will dispatch WebSocket sessions to a service + * using the provided <code>Router</code> instance. + * + * @param router this is the router used to select a service + * @param threads this is the number of threads to use + * @param ping this is the frequency used to send ping frames + */ + public ServiceDispatcher(Router router, int threads, long ping) throws IOException { + this.scheduler = new ConcurrentScheduler(FrameCollector.class, threads); + this.reactor = new ExecutorReactor(scheduler); + this.builder = new SessionBuilder(scheduler, reactor, ping); + this.dispatcher = new SessionDispatcher(builder, router); + } + + /** + * This method is used to create a dispatch a <code>Session</code> to + * a specific service selected by a router. If the session initiating + * handshake fails for any reason this will close the underlying TCP + * connection and send a HTTP 400 response back to the client. + * + * @param request this is the session initiating request + * @param response this is the session initiating response + */ + public void dispatch(Request request, Response response) { + dispatcher.dispatch(request, response); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceEvent.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceEvent.java new file mode 100644 index 0000000..a5d0079 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceEvent.java @@ -0,0 +1,97 @@ +/* + * ServiceEvent.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +/** + * The <code>ServiceEvent</code> enumeration contains the events that + * are dispatched processing a WebSocket. To see how a WebSocket is + * behaving and to gather performance statistics the service events + * can be intercepted using a custom <code>TraceAnalyzer</code> object. + * + * @author Niall Gallagher + * + * @see org.simpleframework.transport.trace.TraceAnalyzer + */ +public enum ServiceEvent { + + /** + * This event is dispatched when a WebSocket is connected. + */ + OPEN_SOCKET, + + /** + * This event is dispatched when a WebSocket is dispatched. + */ + DISPATCH_SOCKET, + + /** + * This event is dispatched when a WebSocket channel is closed. + */ + TERMINATE_SOCKET, + + /** + * This event is dispatched when the response handshake is sent. + */ + WRITE_HEADER, + + /** + * This event is dispatched when the WebSocket receives a ping. + */ + READ_PING, + + /** + * This event is dispatched when a ping is sent over a WebSocket. + */ + WRITE_PING, + + /** + * This event is dispatched when the WebSocket receives a pong. + */ + READ_PONG, + + /** + * This event is dispatched when a pong is sent over a WebSocket. + */ + WRITE_PONG, + + /** + * This event is dispatched when a frame is read from a WebSocket. + */ + READ_FRAME, + + /** + * This event is dispatched when a frame is sent over a WebSocket. + */ + WRITE_FRAME, + + /** + * This indicates that there has been no response to a ping. + */ + PING_EXPIRED, + + /** + * This indicates that there has been no response to a ping. + */ + PONG_RECEIVED, + + /** + * This event is dispatched when an error occurs with a WebSocket. + */ + ERROR; +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceSession.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceSession.java new file mode 100644 index 0000000..b8fc083 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/ServiceSession.java @@ -0,0 +1,139 @@ +/* + * ServiceSession.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import java.util.Map; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.http.socket.FrameChannel; +import org.simpleframework.http.socket.Session; + +/** + * The <code>ServiceSession</code> represents a simple WebSocket session + * that contains the connection handshake details and the actual socket. + * In order to determine how the session should be interacted with the + * protocol is conveniently exposed, however all attributes of the + * original HTTP request are available. + * + * @author Niall Gallagher + * + * @see org.simpleframework.http.socket.FrameChannel + */ +class ServiceSession implements Session { + + /** + * The WebSocket used for asynchronous full duplex communication. + */ + private final FrameChannel channel; + + /** + * This is the initiating response associated with the session. + */ + private final Response response; + + /** + * This is the initiating request associated with the session. + */ + private final Request request; + + /** + * This is the bag of attributes used by this session. + */ + private final Map attributes; + + /** + * Constructor for the <code>ServiceSession</code> object. This is used + * to create the session that will be used by a <code>Service</code> to + * send and receive WebSocket frames. + * + * @param channel this is the actual WebSocket for the session + * @param request this is the session initiating request + * @param response this is the session initiating response + */ + public ServiceSession(FrameChannel channel, Request request, Response response) { + this.channel = new ServiceChannel(channel); + this.attributes = request.getAttributes(); + this.response = response; + this.request = request; + } + + /** + * This can be used to retrieve the response attributes. These can + * be used to keep state with the response when it is passed to + * other systems for processing. Attributes act as a convenient + * model for storing objects associated with the response. This + * also inherits attributes associated with the client connection. + * + * @return the attributes of that have been set on the request + */ + public Map getAttributes() { + return attributes; + } + + /** + * This is used as a shortcut for acquiring attributes for the + * response. This avoids acquiring the attribute <code>Map</code> + * in order to retrieve the attribute directly from that object. + * The attributes contain data specific to the response. + * + * @param key this is the key of the attribute to acquire + * + * @return this returns the attribute for the specified name + */ + public Object getAttribute(Object key) { + return attributes.get(key); + } + + /** + * Provides a <code>WebSocket</code> that can be used to communicate + * with the connected client. Communication is full duplex and also + * asynchronous through the use of a <code>FrameListener</code> that + * can be registered with the socket. + * + * @return a web socket for full duplex communication + */ + public FrameChannel getChannel() { + return channel; + } + + /** + * Provides the <code>Request</code> used to initiate the session. + * This is useful in establishing the identity of the user, acquiring + * an security information and also for determining the request path + * that was used, which be used to establish context. + * + * @return the request used to initiate the session + */ + public Request getRequest() { + return request; + } + + /** + * Provides the <code>Response</code> used to establish the session + * with the remote client. This is useful in establishing the protocol + * used to create the session and also for determining various other + * useful contextual information. + * + * @return the response used to establish the session + */ + public Response getResponse() { + return response; + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionBuilder.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionBuilder.java new file mode 100644 index 0000000..59864ee --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionBuilder.java @@ -0,0 +1,93 @@ +/* + * SessionBuilder.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import java.io.IOException; + +import org.simpleframework.common.thread.Scheduler; +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.http.socket.Session; +import org.simpleframework.transport.reactor.Reactor; + +/** + * The <code>SessionBuilder</code> object is used to create sessions + * for connected WebSockets. Before the session is created a response + * is sent back to the connected client. If for some reason the session + * is not valid or does not conform to the requirements of RFC 6455 + * then a HTTP 400 response code is sent and the TCP channel is closed. + * + * @author Niall Gallagher + */ +class SessionBuilder { + + /** + * This is the scheduler that is used to ping WebSocket sessions. + */ + private final Scheduler scheduler; + + /** + * This is the reactor used to register for I/O notifications. + */ + private final Reactor reactor; + + /** + * This is the frequency the server should send out ping frames. + */ + private final long ping; + + /** + * Constructor for the <code>SessionBuilder</code> object. This is + * used to create sessions using the request and response associated + * with the WebSocket opening handshake. + * + * @param scheduler this is the shared thread pool used for pinging + * @param reactor this is used to check for I/O notifications + * @param ping this is the frequency to send out ping frames + */ + public SessionBuilder(Scheduler scheduler, Reactor reactor, long ping) { + this.scheduler = scheduler; + this.reactor = reactor; + this.ping = ping; + } + + /** + * This is used to create a WebSocket session. If at any point there + * is an error creating the session the underlying TCP connection is + * closed and a <code>Session</code> is returned regardless. + * + * @param request this is the request associated with this session + * @param response this is the response associated with this session + * + * @return this returns the session associated with the WebSocket + */ + public Session create(Request request, Response response) throws Exception { + FrameConnection connection = new FrameConnection(request, response, reactor); + ResponseBuilder builder = new ResponseBuilder(request, response); + StatusChecker checker = new StatusChecker(connection, request, scheduler, ping); + + try { + builder.commit(); + checker.start(); + } catch(Exception e) { + throw new IOException("Could not send response", e); + } + return connection.open(); + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionDispatcher.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionDispatcher.java new file mode 100644 index 0000000..6543be0 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/SessionDispatcher.java @@ -0,0 +1,111 @@ +/* + * SessionDispatcher.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.socket.service.ServiceEvent.DISPATCH_SOCKET; +import static org.simpleframework.http.socket.service.ServiceEvent.ERROR; +import static org.simpleframework.http.socket.service.ServiceEvent.TERMINATE_SOCKET; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.http.socket.Session; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>SessionDispatcher</code> object is used to perform the + * opening handshake for a WebSocket session. Once the session has been + * established it is connected to a <code>Service</code> where frames + * can be sent and received. If for any reason the handshake fails + * this will terminated the connection with a HTTP 400 response. + * + * @author Niall Gallagher + */ +class SessionDispatcher { + + /** + * This is used to create the session for the WebSocket. + */ + private final SessionBuilder builder; + + /** + * This is used to select the service to dispatch to. + */ + private final Router router; + + /** + * Constructor for the <code>SessionDispatcher</code> object. The + * dispatcher created will dispatch WebSocket sessions to a service + * using the provided <code>Router</code> instance. + * + * @param builder this is used to build the WebSocket session + * @param router this is used to select the service + */ + public SessionDispatcher(SessionBuilder builder, Router router) { + this.builder = builder; + this.router = router; + } + + /** + * This method is used to create a dispatch a <code>Session</code> to + * a specific service selected by a router. If the session initiating + * handshake fails for any reason this will close the underlying TCP + * connection and send a HTTP 400 response back to the client. + * + * @param request this is the session initiating request + * @param response this is the session initiating response + */ + public void dispatch(Request request, Response response) { + Channel channel = request.getChannel(); + Trace trace = channel.getTrace(); + + try { + Service service = router.route(request, response); + Session session = builder.create(request, response); + + trace.trace(DISPATCH_SOCKET); + service.connect(session); + } catch(Exception cause) { + trace.trace(ERROR, cause); + terminate(request, response); + } + } + + /** + * This method is used to terminate the connection and commit the + * response. Terminating the session before it has been dispatched + * is done when there is a protocol or an unexpected I/O error with + * the underlying TCP channel. + * + * @param request this is the session initiating request + * @param response this is the session initiating response + */ + public void terminate(Request request, Response response) { + Channel channel = request.getChannel(); + Trace trace = channel.getTrace(); + + try { + response.close(); + channel.close(); + trace.trace(TERMINATE_SOCKET); + } catch(Exception cause) { + trace.trace(ERROR, cause); + } + } +} diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusChecker.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusChecker.java new file mode 100644 index 0000000..1f4f0d7 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusChecker.java @@ -0,0 +1,220 @@ +/* + * StatusChecker.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import static org.simpleframework.http.socket.CloseCode.INTERNAL_SERVER_ERROR; +import static org.simpleframework.http.socket.CloseCode.NORMAL_CLOSURE; +import static org.simpleframework.http.socket.FrameType.PING; +import static org.simpleframework.http.socket.service.ServiceEvent.ERROR; +import static org.simpleframework.http.socket.service.ServiceEvent.PING_EXPIRED; +import static org.simpleframework.http.socket.service.ServiceEvent.PONG_RECEIVED; +import static org.simpleframework.http.socket.service.ServiceEvent.WRITE_PING; + +import java.util.concurrent.atomic.AtomicLong; + +import org.simpleframework.common.thread.Scheduler; +import org.simpleframework.http.Request; +import org.simpleframework.http.socket.DataFrame; +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.Reason; +import org.simpleframework.transport.Channel; +import org.simpleframework.transport.trace.Trace; + +/** + * The <code>StatusChecker</code> object is used to perform health + * checks on connected sessions. Health is determined using the ping + * pong protocol defined in RFC 6455. The ping pong protocol requires + * that any endpoint must respond to a ping control frame with a pong + * control frame containing the same payload. This session checker + * will send out out ping controls frames and wait for a pong frame. + * If it does not receive a pong frame after a configured expiry time + * then it will close the associated session. + * + * @author Niall Gallagher + */ +class StatusChecker implements Runnable{ + + /** + * This is used to perform the monitoring of the sessions. + */ + private final StatusResultListener listener; + + /** + * This is the WebSocket this this pinger will be monitoring. + */ + private final FrameConnection connection; + + /** + * This is the shared scheduler used to execute this checker. + */ + private final Scheduler scheduler; + + /** + * This is a count of the number of unacknowledged ping frames. + */ + private final AtomicLong counter; + + /** + * This is the underling TCP channel that is being checked. + */ + private final Channel channel; + + /** + * The only reason for a close is for an unexpected error. + */ + private final Reason normal; + + /** + * The only reason for a close is for an unexpected error. + */ + private final Reason error; + + /** + * This is used to trace various events for this pinger. + */ + private final Trace trace; + + /** + * This is the frame that contains the ping to send. + */ + private final Frame frame; + + /** + * This is the frequency with which the checker should run. + */ + private final long frequency; + + /** + * Constructor for the <code>StatusChecker</code> object. This + * is used to create a pinger that will send out ping frames at + * a specified interval. If a session does not respond within + * three times the duration of the ping the connection is reset. + * + * @param connection this is the WebSocket to send the frames + * @param request this is the associated request + * @param scheduler this is the scheduler used to execute this + * @param frequency this is the frequency with which to ping + */ + public StatusChecker(FrameConnection connection, Request request, Scheduler scheduler, long frequency) { + this.listener = new StatusResultListener(this); + this.error = new Reason(INTERNAL_SERVER_ERROR); + this.normal = new Reason(NORMAL_CLOSURE); + this.frame = new DataFrame(PING); + this.counter = new AtomicLong(); + this.channel = request.getChannel(); + this.trace = channel.getTrace(); + this.connection = connection; + this.scheduler = scheduler; + this.frequency = frequency; + } + + /** + * This is used to kick of the status checking. Here an initial + * ping is sent over the socket and the task is then scheduled to + * check the result after the frequency period has expired. If + * this method fails for any reason the TCP channel is closed. + */ + public void start() { + try { + connection.register(listener); + trace.trace(WRITE_PING); + connection.send(frame); + counter.getAndIncrement(); + scheduler.execute(this, frequency); + } catch(Exception cause) { + trace.trace(ERROR, cause); + channel.close(); + } + } + + /** + * This method is used to check to see if a session has expired. + * If there have been three unacknowledged ping events then this + * will force a closure of the WebSocket connection. This is done + * to ensure only healthy connections are maintained within the + * server, also RFC 6455 recommends using the ping pong protocol. + */ + public void run() { + long count = counter.get(); + + try { + if(count < 3) { + trace.trace(WRITE_PING); + connection.send(frame); + counter.getAndIncrement(); + scheduler.execute(this, frequency); // schedule the next one + } else { + trace.trace(PING_EXPIRED); + connection.close(normal); + } + } catch (Exception cause) { + trace.trace(ERROR, cause); + channel.close(); + } + } + + /** + * If the connection gets a response to its ping message then this + * will reset the internal counter. This ensure that the connection + * does not time out. If after three pings there is not response + * from the other side then the connection will be terminated. + */ + public void refresh() { + try { + trace.trace(PONG_RECEIVED); + counter.set(0); + } catch(Exception cause) { + trace.trace(ERROR, cause); + channel.close(); + } + } + + /** + * This is used to close the session and send a 1011 close code + * to the client indicating an internal server error. Closing + * of the session in this manner only occurs if there is an + * expiry of the session or an I/O error, both of which are + * unexpected and violate the behaviour as defined in RFC 6455. + */ + public void failure() { + try { + connection.close(error); + channel.close(); + } catch(Exception cause) { + trace.trace(ERROR, cause); + channel.close(); + } + } + + /** + * This is used to close the session and send a 1000 close code + * to the client indicating a normal closure. This will be called + * when there is a close notification dispatched to the status + * listener. Typically here a graceful closure is best. + */ + public void close() { + try { + connection.close(normal); + channel.close(); + } catch(Exception cause) { + trace.trace(ERROR, cause); + channel.close(); + } + } +}
\ No newline at end of file diff --git a/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusResultListener.java b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusResultListener.java new file mode 100644 index 0000000..2b2a049 --- /dev/null +++ b/simple/simple-http/src/main/java/org/simpleframework/http/socket/service/StatusResultListener.java @@ -0,0 +1,93 @@ +/* + * StatusResultListener.java February 2014 + * + * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> + * + * Licensed 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.simpleframework.http.socket.service; + +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.FrameListener; +import org.simpleframework.http.socket.FrameType; +import org.simpleframework.http.socket.Reason; +import org.simpleframework.http.socket.Session; + +/** + * The <code>StatusResultListener</code> is used to listen for responses + * to ping frames sent out by the server. A response to the ping frame + * is a pong frame. When a pong is received it allows the session to + * be scheduled to receive another ping. + * + * @author Niall Gallagher + */ +class StatusResultListener implements FrameListener { + + /** + * This is used to ping sessions to check for health. + */ + private final StatusChecker checker; + + /** + * Constructor for the <code>StatusResultListener</code> object. + * This requires the session health checker that performs the pings + * so that it can reschedule the session for multiple pings if + * the connection responds with a pong. + * + * @param checker this is the session health checker + */ + public StatusResultListener(StatusChecker checker) { + this.checker = checker; + } + + /** + * This is called when a new frame arrives on the WebSocket. If + * the frame is a pong then this will reschedule the the session + * to receive another ping frame. + * + * @param session this is the associated session + * @param frame this is the frame that has been received + */ + public void onFrame(Session session, Frame frame) { + FrameType type = frame.getType(); + + if(type.isPong()) { + checker.refresh(); + } + } + + /** + * This is called when there is an error with the connection. + * When called the session is removed from the checker and no + * more ping frames are sent. + * + * @param session this is the associated session + * @param cause this is the cause of the error + */ + public void onError(Session session, Exception cause) { + checker.failure(); + } + + /** + * This is called when the connection is closed from the other + * side. When called the session is removed from the checker + * and no more ping frames are sent. + * + * @param session this is the associated session + * @param reason this is the reason the connection was closed + */ + public void onClose(Session session, Reason reason) { + checker.close(); + } +}
\ No newline at end of file |