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