/* Copyright (c) 2006, Sriram Srinivasan * * You may distribute this software under the terms of the license * specified in the file "License" */ package kilim.http; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import kilim.Constants; import kilim.Pausable; import kilim.nio.EndPoint; import kilim.nio.ExposedBaos; /** * The response object encapsulates the header and often, but not always, the content. The caller must set all the * fields, except for the protocol, server and date. The body of the response (the content) is written to a stream * obtained from {@link #getOutputStream()}. */ public class HttpResponse extends HttpMsg { // Status codes public static final byte[] ST_CONTINUE = "100 Continue\r\n".getBytes(); public static final byte[] ST_SWITCHING_PROTOCOLS = "101 Switching Protocols\r\n" .getBytes(); // Successful status codes public static final byte[] ST_OK = "200 OK\r\n".getBytes(); public static final byte[] ST_CREATED = "201 Created\r\n".getBytes(); public static final byte[] ST_ACCEPTED = "202 Accepted\r\n".getBytes(); public static final byte[] ST_NON_AUTHORITATIVE = "203 Non-Authoritative Information\r\n" .getBytes(); public static final byte[] ST_NO_CONTENT = "204 No Content\r\n".getBytes(); public static final byte[] ST_RESET_CONTENT = "205 Reset Content\r\n" .getBytes(); public static final byte[] ST_PARTIAL_CONTENT = "206 Partial Content\r\n" .getBytes(); // Redirection status codes public static final byte[] ST_MULTIPLE_CHOICES = "300 Multiple Choices\r\n" .getBytes(); public static final byte[] ST_MOVED_PERMANENTLY = "301 Moved Permanently\r\n" .getBytes(); public static final byte[] ST_FOUND = "302 Found\r\n".getBytes(); public static final byte[] ST_SEE_OTHER = "303 See Other\r\n".getBytes(); public static final byte[] ST_NOT_MODIFIED = "304 Not Modified\r\n" .getBytes(); public static final byte[] ST_USE_PROXY = "305 Use Proxy\r\n".getBytes(); public static final byte[] ST_TEMPORARY_REDIRECT = "307 Temporary Redirect\r\n" .getBytes(); // Client error codes public static final byte[] ST_BAD_REQUEST = "400 Bad Request\r\n".getBytes(); public static final byte[] ST_UNAUTHORIZED = "401 Unauthorized\r\n" .getBytes(); public static final byte[] ST_PAYMENT_REQUIRED = "402 Payment Required\r\n" .getBytes(); public static final byte[] ST_FORBIDDEN = "403 Forbidden\r\n".getBytes(); public static final byte[] ST_NOT_FOUND = "404 Not Found\r\n".getBytes(); public static final byte[] ST_METHOD_NOT_ALLOWED = "405 Method Not Allowed\r\n" .getBytes(); public static final byte[] ST_NOT_ACCEPTABLE = "406 Not Acceptable\r\n" .getBytes(); public static final byte[] ST_PROXY_AUTHENTICATION_REQUIRED = "407 Proxy Authentication Required\r\n" .getBytes(); public static final byte[] ST_REQUEST_TIMEOUT = "408 Request Time-out\r\n" .getBytes(); public static final byte[] ST_CONFLICT = "409 Conflict\r\n".getBytes(); public static final byte[] ST_GONE = "410 Gone\r\n".getBytes(); public static final byte[] ST_LENGTH_REQUIRED = "411 Length Required\r\n" .getBytes(); public static final byte[] ST_PRECONDITION_FAILED = "412 Precondition Failed\r\n" .getBytes(); public static final byte[] ST_REQUEST_ENTITY_TOO_LARGE = "413 Request Entity Too Large\r\n" .getBytes(); public static final byte[] ST_REQUEST_URI_TOO_LONG = "414 Request-URI Too Large\r\n" .getBytes(); public static final byte[] ST_UNSUPPORTED_MEDIA_TYPE = "415 Unsupported Media Type\r\n" .getBytes(); public static final byte[] ST_REQUEST_RANGE_NOT_SATISFIABLE = "416 Requested range not satisfiable\r\n" .getBytes(); public static final byte[] ST_EXPECTATION_FAILED = "417 Expectation Failed\r\n" .getBytes(); // Server error codes public static final byte[] ST_INTERNAL_SERVER_ERROR = "500 Internal Server Error\r\n" .getBytes(); public static final byte[] ST_NOT_IMPLEMENTED = "501 Not Implemented\r\n" .getBytes(); public static final byte[] ST_BAD_GATEWAY = "502 Bad Gateway\r\n".getBytes(); public static final byte[] ST_SERVICE_UNAVAILABLE = "503 Service Unavailable\r\n" .getBytes(); public static final byte[] ST_GATEWAY_TIMEOUT = "504 Gateway Time-out\r\n" .getBytes(); public static final byte[] ST_HTTP_VERSION_NOT_SUPPORTED = "505 HTTP Version not supported\r\n" .getBytes(); // Http response components public static final byte[] PROTOCOL = "HTTP/1.1 ".getBytes(); public static final byte[] F_SERVER = ("Server: kilim " + Constants.KILIM_VERSION + "\r\n") .getBytes(); public static final byte[] F_DATE = "Date: ".getBytes(); public static final byte[] CRLF = "\r\n".getBytes(); public static final byte[] FIELD_SEP = ": ".getBytes(); public static ConcurrentHashMap<String, byte[]> byteCache = new ConcurrentHashMap<String, byte[]>(); /** * The status line for the response. Can use any of the predefined strings in HttpResponse.ST_*. */ public byte[] status; public ArrayList<String> keys = new ArrayList<String>(); public ArrayList<String> values = new ArrayList<String>(); public ExposedBaos bodyStream; public static final SimpleDateFormat gmtdf; static { gmtdf = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss"); gmtdf.setTimeZone(TimeZone.getTimeZone("GMT:00")); } public HttpResponse() { this(ST_OK); } public HttpResponse(byte[] statusb) { status = statusb; } public void reuse() { status = ST_OK; keys.clear(); values.clear(); if (bodyStream != null) { bodyStream.reset(); } if (buffer != null) { buffer.clear(); } } public void setStatus(String statusMsg) { if (!statusMsg.endsWith("\r\n")) { statusMsg = statusMsg + "\r\n"; } this.status = statusMsg.getBytes(); } public HttpResponse(String statusMsg) { this(); setStatus(statusMsg); } public HttpResponse addField(String key, String value) { keys.add(key); values.add(value); return this; } public String getHeaderValue(String key) { int nfields = keys.size(); for (int i = 0; i < nfields; i++) { if (key.equals(keys.get(i))) return values.get(i); } return null; } public void writeHeader(OutputStream os) throws IOException { DataOutputStream dos = new DataOutputStream(os); dos.write(PROTOCOL); dos.write(status); dos.write(F_DATE); byte[] date = gmtdf.format(new Date()).getBytes(); dos.write(date); dos.write(CRLF); dos.write(F_SERVER); if (bodyStream != null && getHeaderValue("Content-Length") == null) { setContentLength(bodyStream.size()); } // Fields. int nfields = keys.size(); for (int i = 0; i < nfields; i++) { String key = keys.get(i); byte[] keyb = byteCache.get(key); if (keyb == null) { keyb = key.getBytes(); byteCache.put(key, keyb); } dos.write(keyb); dos.write(FIELD_SEP); dos.write(values.get(i).getBytes()); dos.write(CRLF); } dos.write(CRLF); } public OutputStream getOutputStream() { if (bodyStream == null) bodyStream = new ExposedBaos(2048); return bodyStream; } public void writeTo(EndPoint endpoint) throws IOException, Pausable { ExposedBaos headerStream = new ExposedBaos(); writeHeader(headerStream); ByteBuffer bb = headerStream.toByteBuffer(); endpoint.write(bb); if (bodyStream != null && bodyStream.size() > 0) { bb = bodyStream.toByteBuffer(); endpoint.write(bb); } } public void setContentLength(long length) { addField("Content-Length", Long.toString(length)); } public void setContentType(String contentType) { addField("Content-Type", contentType); } }