diff options
Diffstat (limited to 'simple/simple-http/src/main/java/org/simpleframework/http/core')
31 files changed, 5608 insertions, 0 deletions
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 |