/* * Transfer.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>Transfer</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.Producer */ class Transfer { /** * This is used to create a producer based on the HTTP headers. */ private ProducerFactory 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 Producer producer; /** * This is the trace used to monitor events in the data transfer. */ private Trace trace; /** * Constructor for the <code>Transfer</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 support * this is used to determine the semantics * @param sender * this is used to send data over the transport * @param monitor * this is used to signal I/O events to the kernel */ public Transfer(Response response, Conversation support, Channel channel, Monitor monitor) { this.factory = new ProducerFactory(support, channel, monitor); 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 producer 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 this.producer != 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 (this.producer != null) throw new TransferException("Transfer has already started"); this.clear(); this.configure(); this.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 (this.producer != null) throw new TransferException("Transfer has already started"); this.clear(); this.configure(length); this.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 producer 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 { this.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 producer 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 (this.producer == null) throw new TransferException("Conversation details not ready"); this.trace.trace(WRITE_BODY, len); this.producer.produce(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 producer 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 TransferException("Buffer position greater than limit"); this.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 producer 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 (this.producer == null) throw new TransferException("Conversation details not ready"); this.trace.trace(WRITE_BODY, len); this.producer.produce(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 (this.producer == null) throw new TransferException("Conversation details not ready"); this.producer.flush(); } /** * 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 (this.producer == null) throw new TransferException("Conversation details not ready"); this.producer.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 = this.support.getContentLength(); boolean empty = this.support.isEmpty(); if (empty) { this.support.setContentLength(0); } else if (length >= 0) { this.support.setContentLength(length); } else { this.support.setChunkedEncoded(); } this.producer = this.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 = this.support.getContentLength(); if (this.support.isHead()) { if (count > 0) { this.configure(count, count); } else { this.configure(count, length); } } else { this.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 = this.support.isEmpty(); if (empty) { this.support.setContentLength(0); } else if (length >= 0) { this.support.setContentLength(length); } else { this.support.setChunkedEncoded(); } this.producer = this.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 { this.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 * producer is created. */ private void commit() throws IOException { try { this.response.commit(); } catch (Exception cause) { throw new TransferException("Unable to commit", cause); } } }