// Reply.java
// $Id: Reply.java,v 1.38 2003/02/25 17:52:01 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package org.w3c.jigsaw.http ;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import org.w3c.www.mime.MimeType;
import org.w3c.www.http.HTTP;
import org.w3c.www.http.HeaderValue;
import org.w3c.www.http.HttpEntityMessage;
import org.w3c.www.http.HttpFactory;
import org.w3c.www.http.HttpMessage;
import org.w3c.www.http.HttpMimeType;
import org.w3c.www.http.HttpReplyMessage;
import org.w3c.www.http.HttpRequestMessage;
import org.w3c.www.http.HttpTokenList;
import org.w3c.tools.resources.ReplyInterface;
import org.w3c.tools.resources.RequestInterface;
import org.w3c.tools.resources.ResourceFilter;
public class Reply extends HttpReplyMessage implements ReplyInterface {
protected static HttpMimeType DEFAULT_TYPE = null;
protected static HttpTokenList CONNECTION = null;
private static String ka = "Keep-Alive";
private static String pc = "Proxy-Connection";
private static String cl = "close";
static {
String connection[] = { "Keep-Alive" };
CONNECTION = HttpFactory.makeStringList(connection);
DEFAULT_TYPE = HttpFactory.makeMimeType(MimeType.TEXT_HTML);
}
InputStream is = null;
Client client = null;
boolean keep = true;
Request request = null;
private boolean sendBody = true;
private boolean dynamic = false;
/**
* set the reply to be a reply for dynamic content
* @param a boolean, true if the reply is generated by a dynamic
*/
public void setDynamic(boolean dyn) {
dynamic = dyn;
}
/**
* is is dynamic or not?
*/
public boolean isDynamic() {
return dynamic;
}
public void setStatus(Integer status) {
setStatus(status.intValue());
}
public boolean hasContentLength() {
return hasHeader(H_CONTENT_LENGTH);
}
public boolean hasContentType() {
return ( hasHeader(H_CONTENT_TYPE) &&
( getHeaderValue(H_CONTENT_TYPE).getValue() != null));
}
public void setKeepAlive(String value) {
setValue(ka, value);
}
public void setProxyConnection(String value) {
setValue(pc, value);
}
public boolean keepProxyConnection() {
throw new RuntimeException("keepProxyConnection: not implemented!");
}
/**
* @deprecated
*/
public FileDescriptor getInputFileDescriptor() {
return null;
}
public void setKeepConnection(boolean onoff) {
this.keep = onoff;
}
public boolean tryKeepConnection() {
if ( ! keep ) {
addConnection(cl);
return false;
} else if ( major >= 1 ) {
if ( minor >= 1 )
return true;
if ( hasContentLength() || (is == null)) {
if ( is_proxy )
addProxyConnection(ka);
else
addConnection(ka);
return true;
}
}
return false;
}
/**
* Is this reply a proxy reply.
*/
protected boolean is_proxy = false;
/**
* Mark this reply as being a proxy reply.
*/
public void setProxy (boolean onoff) {
is_proxy = onoff;
}
/**
* Sets the reply stream to the given HtmlGenerator stream.
* @param g The HtmlGenerator whose output is to be used as the reply body.
*/
public void setStream (org.w3c.jigsaw.html.HtmlGenerator g) {
g.close() ;
setContentLength (g.length()) ;
setContentType (g.getMimeType()) ;
if (sendBody)
setStream (g.getInputStream(), true) ;
}
public boolean hasStream() {
return is != null;
}
/**
* Open this reply body stream.
* This is used to send the reply body back to the client.
* @return An InputStream containing the reply body, which is dumped
* back to the client.
*/
public InputStream openStream () {
return is ;
}
public void setStream(InputStream is) {
setStream(is, false);
}
public synchronized void setStream(InputStream is, boolean closeOld) {
if (sendBody) {
if (closeOld && (this.is != null)) {
try {
this.is.close();
} catch (IOException ioex) {};
}
this.is = is;
}
}
protected ResourceFilter filters[] = null;
protected int infilters = -1;
protected void setFilters(ResourceFilter filters[], int infilters) {
this.filters = filters;
this.infilters = infilters;
}
protected OutputStream output = null;
/**
* Get the reply output stream.
* @param doEmit Emit that reply before giving out the output stream.
* @return An OutputStream instance.
* @exception IOException If the output stream could not get opened.
*/
public synchronized OutputStream getOutputStream(boolean doEmit)
throws IOException
{
if ( output != null )
return output;
// Build the output stream:
output = client.getOutputStream();
// Call any filters:
while ( --infilters >= 0 )
output = filters[infilters].outputFilter(request, this, output);
if ( doEmit ) {
DataOutputStream dataOutput = new DataOutputStream(output);
emit(dataOutput);
dataOutput.flush();
setStatus(HTTP.DONE);
}
// Disable keep-connection:
keep = false;
return output;
}
/**
* Get that reply output stream.
* The reply is first emitted to the stream, and the opened stream
* is returned back to the caller.
* @return An OutputStream instance.
* @exception IOException If the output stream could not get opened.
*/
public OutputStream getOutputStream()
throws IOException
{
return getOutputStream(true);
}
/**
* Should this reply be chunked ?
* @return If so, the reply should prepare itself to send back the
* appropriate transfer encoding header, and return
* <strong>true</strong>, otherwise it should just return
* <strong>false</strong>.
*/
protected Boolean chunkable = null ;
// FIXME should be an HttpTokenList
protected static String chunked = "chunked";
public boolean canChunkTransfer() {
// Have we already compute this ?
if ( chunkable == null ) {
// Compute wether we can chunk the reply:
if ( hasContentLength() || (is == null)) {
chunkable = Boolean.FALSE ;
} else if ((major >= 1) && (minor >= 1)) {
// String connections[] = getConnection();
chunkable = Boolean.TRUE ;
// if (connections != null) {
// for (int i = 0; i< connections.length; i++) {
// if (connections[i].equalsIgnoreCase("close")) {
// chunkable = Boolean.FALSE;
// }
// }
// }
// if (chunkable == Boolean.TRUE)
addTransferEncoding(chunked);
} else {
chunkable = Boolean.FALSE ;
}
}
return (chunkable == Boolean.TRUE) ;
}
/**
* Set this reply content.
* This method allows to set the reply content to a simple String instance.
* @param msg The reply content.
* @param encoding, the encoding of the reply
*/
public void setContent (String msg, String encoding) {
if ( ! hasContentType() )
setHeaderValue(H_CONTENT_TYPE, DEFAULT_TYPE) ;
byte byteBuffer[];
try {
byteBuffer = msg.getBytes(encoding) ;
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException (this.getClass().getName() +
"[setContent] Unable to convert" +
"properly char to bytes");
}
setContentLength (byteBuffer.length) ;
if (sendBody) {
ByteArrayInputStream bis = new ByteArrayInputStream (byteBuffer) ;
setStream(bis, true);
}
}
// FIXME the bug fix should be in HttpMessage.hasHeader, but it would
// be more time consuming, as it would add checks for all headers
// and only Content-Type seems to be problematic
/**
* @param out The output stream to emit the message to.
* @param what (fixme doc)
* @exception IOException If the message couldn't be emited to the
* given stream, due to IO errors.
*/
public void emit(OutputStream out, int what)
throws IOException
{
int status = getStatus();
if (!hasContentType() && ((status != HTTP.CONTINUE) ||
(status != HTTP.SWITCHING))) {
setHeaderValue(H_CONTENT_TYPE, DEFAULT_TYPE) ;
}
super.emit(out, what);
}
public void dump(OutputStream out) {
if (!hasContentType()) {
setHeaderValue(H_CONTENT_TYPE, DEFAULT_TYPE) ;
}
super.dump(out);
}
/**
* Set this reply content.
* This method allows to set the reply content to a simple String instance.
* encoding will be by default "ISO8859_1"
* @param msg The reply content.
*/
public void setContent (String msg) {
setContent(msg, "ISO-8859-1");
}
/**
* Get the entity MIME type.
* @return An HttpMimeType object describing the entity's type, or
* <strong>null</strong> if udefined.
*/
public MimeType getContentType() {
MimeType mt = super.getContentType();
if (mt == null) {
return MimeType.APPLICATION_OCTET_STREAM;
} else {
return mt;
}
}
/**
* Create a new Reply instance for the given client.
* @param client The client to who this reply is directed.
*/
public Reply (Client client) {
this.client = client ;
}
/**
* Create a new reply for the given client.
* @param client The client ot who the reply is directed.
* @reply status The reply status code.
*/
public Reply(Client client, Request request, short major, short minor,
int status) {
this (client) ;
this.request = request;
this.major = major;
this.minor = minor;
this.keep = true;
this.setServer((client != null)
? client.getServer().getSoftware()
: null);
this.setStatus (status);
if (request != null)
if (request.getMethod().endsWith(HTTP.HEAD))
sendBody = false;
}
}