HttpServletResponse Class

package javaxt.http.servlet;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.StandardOpenOption;
import java.util.Calendar;
import java.util.TimeZone;
import org.eclipse.jetty.server.HttpOutput;


//******************************************************************************
//**  HttpServletResponse Class
//******************************************************************************
/**
 *   Used to generate a response to an HTTP request. This class implements the
 *   javax.servlet.http.HttpServletResponse interface defined in Version 2.5
 *   of the Java Servlet API.
 *
 ******************************************************************************/

public class HttpServletResponse {

    private javax.servlet.http.HttpServletResponse response;
    private HttpServletRequest request;

    private Integer statusCode;
    private String statusMessage;
    private int bufferSize = 8096; //8KB
    private static final String z = "GMT";
    private static final TimeZone tz = TimeZone.getTimeZone(z);
    private String charSet = "UTF-8";
//    private java.util.Locale locale = java.util.Locale.getDefault();
//    private java.util.ArrayList<Cookie> cookies = new java.util.ArrayList<Cookie>();
//    private Long startRange, endRange;

  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new instance of this class. */

    public HttpServletResponse(HttpServletRequest request, javax.servlet.http.HttpServletResponse response) {

        this.request = request;
        this.response = response;


      //Set default response headers for standard HTTP/S requests. WebSockets and
      //other upgrade requests should be handled differently.
        if (request.getHeader("Upgrade")==null){
            //if (request.getHeader("Upgrade")
            //setHeader("Accept-Ranges", "bytes");
            setHeader("Connection", (request.isKeepAlive() ? "Keep-Alive" : "Close"));
            setHeader("Server", request.getServletContext().getServerInfo());
            setHeader("Date", getDate(Calendar.getInstance()));
            setStatus(200, "OK");
        }
    }



  //**************************************************************************
  //** addCookie
  //**************************************************************************
  /** Adds the specified cookie to the response.
   */
    public void addCookie(Cookie cookie){
        response.addCookie(cookie.getCookie());
    }


  //**************************************************************************
  //** setContentLength
  //**************************************************************************
  /** Sets the "Content-Length" in the response header. Note that the
   *  "Content-Length" header is set automatically by most of the write()
   *  methods. So unless you're writing directly to the ServletOutputStream,
   *  you do not need to set this header.
   */
    public void setContentLength(int contentLength){
        response.setContentLengthLong(contentLength);
    }


  //**************************************************************************
  //** setContentLength
  //**************************************************************************
  /** Sets the "Content-Length" in the response header. Note that the
   *  "Content-Length" header is set automatically by most of the write()
   *  methods. So unless you're writing directly to the ServletOutputStream,
   *  you do not need to set this header.
   */
    public void setContentLength(long contentLength){
        response.setContentLengthLong(contentLength);
    }


  //**************************************************************************
  //** getContentLength
  //**************************************************************************
  /** Returns the "Content-Length" defined in the response header. Returns
   *  null if the "Content-Length" is not defined or is less than zero.
   */
    public Long getContentLength(){
        try{
            long l = Long.parseLong(getHeader("Content-Length"));
            if (l<0) return null;
            else return l;
        }
        catch(Exception e){
            return null;
        }
    }


  //**************************************************************************
  //** setContentType
  //**************************************************************************
  /** Used to set/update the "Content-Type" response header
   *  (e.g. "text/html; charset=utf-8").
   */
    public void setContentType(String contentType){
        response.setContentType(contentType);
    }


  //**************************************************************************
  //** getContentType
  //**************************************************************************
  /** Returns the "Content-Type" defined in the response header
   *  (e.g. "text/html; charset=utf-8").
   */
    public String getContentType(){
        return getHeader("Content-Type");
    }


  //**************************************************************************
  //** setCharacterEncoding
  //**************************************************************************
  /** Sets the name of the character encoding used in the response. Default is
   *  "UTF-8".
   *  @param charset String specifying the character set defined by
   *  <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
   */
    public void setCharacterEncoding(String charset) throws java.io.UnsupportedEncodingException {
        response.setCharacterEncoding(charset);
    }


  //**************************************************************************
  //** getCharacterEncoding
  //**************************************************************************
  /** Returns the name of the character encoding used in the response.
   */
    public String getCharacterEncoding(){
        return response.getCharacterEncoding();
    }


  //**************************************************************************
  //** setLocale
  //**************************************************************************
  /** Sets the locale of the response. The locale is communicated via the
   *  "Content-Language" header and the character encoding in the
   *  "Content-Type" header.
   */
    public void setLocale(java.util.Locale locale){
        response.setLocale(locale);
    }


  //**************************************************************************
  //** getLocale
  //**************************************************************************
  /** Returns the locale specified for this response using the setLocale()
   *  method.
   */
    public java.util.Locale getLocale(){
        return response.getLocale();
    }


  //**************************************************************************
  //** addHeader
  //**************************************************************************
  /** Adds a response header with the given name and value. According to spec,
   *  http response headers should be allowed to have multiple values. However,
   *  this implementation does not currently allow response headers to have
   *  multiple values.
   */
    public void addHeader(String name, String value){
        response.addHeader(name, value);
    }


  //**************************************************************************
  //** setHeader
  //**************************************************************************
  /** Sets a response header with the given name and value.
   */
    public void setHeader(String name, String value){


        if (name==null) return;


      //Don't set "Transfer-Encoding" to "chunked" if the client doesn't
      //support it. Servers are explicitly forbidden from sending that
      //particular encoding type to clients announcing themselves as
      //HTTP/1.0 (e.g. Squid 2.5).
        if (name.equalsIgnoreCase("Transfer-Encoding") && value!=null){
            if (value.equalsIgnoreCase("chunked")){
                String httpClient = request.getHttpVersion();
                if (httpClient==null) return;
                if (httpClient.equals("0.9") || httpClient.equals("1.0")){
                    return;
                }
            }
        }

        response.setHeader(name, value);

    }


  //**************************************************************************
  //** getHeader
  //**************************************************************************

    public String getHeader(String name){
        return response.getHeader(name);
    }


  //**************************************************************************
  //** containsHeader
  //**************************************************************************
  /** Returns a boolean indicating whether the named response header has
   *  already been set.
   */
    public boolean containsHeader(String name){
        return response.containsHeader(name);
    }


  //**************************************************************************
  //** setDateHeader
  //**************************************************************************
  /** Sets a response header with the given name and date-value.
   */
    public void setDateHeader(String name, long date){
        response.setDateHeader(name, date);
    }


  //**************************************************************************
  //** addDateHeader
  //**************************************************************************
  /** Adds a response header with the given name and date-value. According to
   *  spec, http response headers should be allowed to have multiple values.
   *  However, this implementation does not currently allow response headers
   *  to have multiple values.
   */
    public void addDateHeader(String name, long date){
        response.addDateHeader(name, date);
    }


  //**************************************************************************
  //** setIntHeader
  //**************************************************************************
  /** Sets a response header with the given name and integer value.
   */
    public void setIntHeader(String name, int value){
        response.setIntHeader(name, value);
    }


  //**************************************************************************
  //** addIntHeader
  //**************************************************************************
  /** Adds a response header with the given name and integer value. According
   *  to spec, http response headers should be allowed to have multiple values.
   *  However, this implementation does not currently allow response headers
   *  to have multiple values.
   */
    public void addIntHeader(String name, int value){
        response.addIntHeader(name, value);
    }


  //**************************************************************************
  //** setBufferSize
  //**************************************************************************
  /** Sets the preferred buffer size for the body of the response. A larger
   *  buffer allows more content to be sent to the client at a time. A smaller
   *  buffer decreases server memory load and allows the client to start
   *  receiving data more quickly.<p/>
   *
   *  This method must be called before any response body content is
   *  written.
   */
    public void setBufferSize(int size){
        response.setBufferSize(size);
    }


  //**************************************************************************
  //** setBufferSize
  //**************************************************************************
  /** Returns the buffer size used for the response.
   */
    public int getBufferSize(){
        return response.getBufferSize();
    }


  //**************************************************************************
  //** setStatus
  //**************************************************************************

    public void setStatus(int sc){
        this.setStatus(sc, getStatusMessage(sc));
    }


    public void setStatus(int statusCode, String statusMessage){
        response.setStatus(statusCode, statusMessage);
    }


    public int getStatus(){
        return response.getStatus();
    }

//    public String getStatusMessage(){
//        return statusMessage;
//    }


  //**************************************************************************
  //** getStatusMessage
  //**************************************************************************
  /** Returns the status message for a given status code. Source:
   *  http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
   */
    protected static String getStatusMessage(int statusCode){
        switch (statusCode) {
            case 100:  return "Continue";
            case 200:  return "OK";
            case 201:  return "Created";
            case 202:  return "Accepted";
            case 203:  return "Partial Information"; //Non-Authoritative Information
            case 204:  return "No Content";
            case 206:  return "Partial Content";
            case 301:  return "Moved Permanently";
            case 302:  return "Found";
            case 304:  return "Not Modified";
            case 307:  return "Temporary Redirect";
            case 400:  return "Bad Request";
            case 401:  return "Unauthorized";
            case 403:  return "Forbidden";
            case 404:  return "Not Found";
            case 500:  return "Internal Error";
            case 501:  return "Not Implemented";
            case 502:  return "Bad Gateway";
            case 503:  return "Service Unavailable";
            case 504:  return "Gateway Timeout";
            case 505:  return "HTTP Version Not Supported";
            default: return null;
        }
   }

  //**************************************************************************
  //** sendError
  //**************************************************************************
  /** Sends an error response to the client using the specified status.  The
   *  server defaults to creating the response to look like an HTML-formatted
   *  server error page containing the specified message, setting the content
   *  type to "text/html", leaving cookies and other headers unmodified.
   *
   *  <p>If the response has already been committed, this method throws
   *  an IllegalStateException.
   *  After using this method, the response should be considered
   *  to be committed and should not be written to.
   *
   * @param	sc	the error status code
   * @param	msg	the descriptive message
   * @exception	IOException	If an input or output exception occurs
   * @exception	IllegalStateException	If the response was committed
   */
    public void sendError(int sc, String msg) throws IOException{

        setStatus(sc, msg);
        write(
            "<head>" +
            "<title>" + sc + " - " + msg + "</title>" +
            "</head>" +
            "<body>" +
            "<h1>" + sc + "</h1>" + msg +
            "</body>"
        );
    }


  //**************************************************************************
  //** sendError
  //**************************************************************************
  /** Sends an error response to the client using the specified status
   *  code and clearing the buffer.
   *  <p>If the response has already been committed, this method throws
   *  an IllegalStateException.
   *  After using this method, the response should be considered
   *  to be committed and should not be written to.
   *
   * @param	sc	the error status code
   * @exception	IOException	If an input or output exception occurs
   * @exception	IllegalStateException	If the response was committed
   *						before this method call
   */
    public void sendError(int sc) throws IOException {
        sendError(sc, getStatusMessage(sc));
    }


  //**************************************************************************
  //** sendRedirect
  //**************************************************************************
  /** Sends a temporary redirect response to the client using the specified
   *  redirect location URL.
   */
    public void sendRedirect(String location) throws IOException {
        sendRedirect(location, false);
    }


  //**************************************************************************
  //** sendRedirect
  //**************************************************************************
  /** Sends a temporary or permanent redirect response to the client using the
   *  specified redirect location URL.
   */
    public void sendRedirect(String location, boolean movedPermanently)
        throws IOException {

        if (movedPermanently) setStatus(301);
        else setStatus(307);
        setHeader("Location", location);
        write(
            "<head>" +
            "<title>Document Moved</title>" +
            "</head>" +
            "<body>" +
            "<h1>Object Moved</h1>" +
            "This document may be found <a href=\"" + location + "\">here</a>" +
            "</body>"
        );
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Used to write a block of text in the response body. You should only call
   *  this method once.
   *  @param compressOutput Specify whether to gzip compress the text.
   *  Note that this option will be applied only if "Accept-Encoding" supports
   *  gzip compression.
   */
    public void write(String text, boolean compressOutput) throws IOException {
        try{
            write(text.getBytes(charSet), compressOutput);
        }
        catch(java.io.UnsupportedEncodingException e){
            //this error should have been thrown earlier (setCharacterEncoding)
        }
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Used to write a block of text in the response body. Will automatically
   *  try to gzip compress the text if "Accept-Encoding" supports gzip
   *  compression. You should only call this method once.
   */
    public void write(String text) throws IOException {
        this.write(text, true);
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Used to write bytes to the response body. You should only call this
   *  method once.
   *  @param bytes Input byte array
   *  @param compressOutput Specify whether to gzip compress the byte array.
   *  Note that this option will be applied only if "Accept-Encoding" supports
   *  gzip compression. Do not use this option if your bytes are already gzip
   *  compressed.
   */
    public void write(byte[] bytes, boolean compressOutput) throws IOException {
        if (bytes==null) return;


      //Check whether we can/should compress the output
        boolean gzip = false;
        if (compressOutput && bytes.length>50){
            String acceptEncoding = request.getHeader("Accept-Encoding");
            if (acceptEncoding!=null){
                if (acceptEncoding.toLowerCase().contains("gzip")){
                    gzip = true;
                }
            }
        }



        if (gzip){

          //If the input byte array is smaller than the bufferSize we can
          //compress the entire array in a single step. Otherwise, we will
          //compress incrementally and chuck out the output.
            if (bytes.length<=bufferSize){

              //Compress the byte array
                ByteArrayOutputStream bos = new ByteArrayOutputStream(bytes.length);
                GZIPOutputStream out = new GZIPOutputStream(bos, bytes.length);
                out.write(bytes);
                out.finish();
                out.close();

                bytes = bos.toByteArray();

                bos.reset();
                bos = null;
                out = null;


              //Set content length. This is extremely important for persistant
              //connections (i.e. "Connection: Keep-Alive").
                setContentLength(bytes.length);

              //Ensure that the Content Encoding is correct and write the header
                setHeader("Content-Encoding", "gzip");

              //Write the body
                ByteBuffer output = ByteBuffer.allocateDirect(bytes.length);
                output.put(bytes);
                output.flip();
                write(output);


                output.clear();
                output = null;

            }
            else{ //Chunk the output

              //Write header before sending the file contents. Ensure that
              //the output is chunked and the content encoding is correct.
                setHeader("Content-Length", null);
                setHeader("Transfer-Encoding", "chunked");
                setHeader("Content-Encoding", "gzip");


              //Incrementally compress the byte array and chunk the output
                HttpOutput out = (HttpOutput) response.getOutputStream();
                out.setBufferSize(bufferSize);
                GZIPOutputStream gz = new GZIPOutputStream(out, bufferSize);
                java.io.InputStream inputStream = new ByteArrayInputStream(bytes);
                byte[] b = new byte[bufferSize];
                int x=0;
                while ( (x = inputStream.read(b)) != -1) {
                    gz.write(b,0,x);
                }
                inputStream.close();

                gz.finish();
                gz.close();

                gz = null;
                b = null;

            }
        }
        else{ //no compression

          //Write header before sending the file contents.
            setContentLength(bytes.length);


          //Send the contents of the byte array to the client
            java.io.InputStream inputStream = new ByteArrayInputStream(bytes);
            HttpOutput out = (HttpOutput) response.getOutputStream();
            out.setBufferSize(bufferSize);
            byte[] b = new byte[bufferSize];

            int x=0;
            while ( (x = inputStream.read(b)) != -1) {
                out.write(b,0,x);
            }

            inputStream.close();
            inputStream = null;

            out.close();
            out = null;

            b = null;
        }
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Used to write bytes to the response body. Will automatically try to
   *  compress the byte array if "Accept-Encoding" supports gzip compression.
   *  You should only call this method once.
   */
    public void write(byte[] bytes) throws IOException {
        this.write(bytes, true);
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Used to write contents of a file into the response body. Automatically
   *  compresses the file content if the client supports gzip compression.
   *  You should only call this method once.
   */
    public void write(java.io.File file, String contentType, boolean useCache)
        throws IOException {

        String fileName = null;
        if (!contentType.startsWith("image") && !contentType.startsWith("text")){
            fileName = file.getName();
        }

        write(file, fileName, contentType, useCache);
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Used to write contents of a file into the response body. Automatically
   *  compresses the file content if the client supports gzip compression.
   *  You should only call this method once.
   *  @param fileName Optional file name used in the "Content-Disposition" header.
   *  If the fileName is null, the "Content-Disposition" header will not be
   *  set by this method.
   */
    public void write(java.io.File file, String fileName, String contentType, boolean useCache)
        throws IOException {

        if (!file.exists() || file.isDirectory()){
            this.setStatus(404);
            return;
        }

        long fileSize = file.length();
        long fileDate = file.lastModified();


      //Process Cache Directives
        if (useCache){

            String eTag = "W/\"" + fileSize + "-" + fileDate + "\"";
            setHeader("ETag", eTag);
            setHeader("Last-Modified", getDate(fileDate)); //Sat, 23 Oct 2010 13:04:28 GMT
            //this.setHeader("Cache-Control", "max-age=315360000");
            //this.setHeader("Expires", "Sun, 30 Sep 2018 16:23:15 GMT  ");


          //Return 304/Not Modified response if we can...
            String matchTag = request.getHeader("if-none-match");
            String cacheControl = request.getHeader("cache-control");
            if (matchTag==null) matchTag = "";
            if (cacheControl==null) cacheControl = "";
            if (cacheControl.equalsIgnoreCase("no-cache")==false){
                if (eTag.equalsIgnoreCase(matchTag)){
                    //System.out.println("Sending 304 Response!");
                    this.setStatus(304);
                    return;
                }
                else{
                  //Internet Explorer 6 uses "if-modified-since" instead of "if-none-match"
                    matchTag = request.getHeader("if-modified-since");
                    if (matchTag!=null){
                        for (String tag: matchTag.split(";")){
                            if (tag.trim().equalsIgnoreCase(getDate(fileDate))){
                                //System.out.println("Sending 304 Response!");
                                this.setStatus(304);
                                return;
                            }
                        }
                    }

                }
            }

        }
        else{
            setHeader ("Cache-Control", "no-cache");
        }


      //Set Content Type and Disposition
        setContentType(contentType);
        if (fileName!=null){
            setHeader("Content-Disposition", "attachment;filename=\"" + fileName + "\"");
        }


      //If the file is small enough, send the file.
        if (fileSize<=bufferSize){
            setContentLength(fileSize);
            HttpOutput out = (HttpOutput) response.getOutputStream();
            out.setBufferSize(bufferSize);
            out.sendContent(FileChannel.open(file.toPath(), StandardOpenOption.READ));
            return;
        }


      //Check whether to compress the response
        boolean gzip = false;
        String acceptEncoding = request.getHeader("Accept-Encoding");
        if (acceptEncoding!=null){
            if (acceptEncoding.toLowerCase().contains("gzip")){
                gzip = true;
            }
        }



      //Dump file contents to servlet output stream
        if (gzip){

          //Write header before sending the file contents. Ensure that the
          //output is chunked and the content encoding is correct.
            setHeader("Content-Length", null);
            setHeader("Transfer-Encoding", "chunked");
            setHeader("Content-Encoding", "gzip");


            HttpOutput out = (HttpOutput) response.getOutputStream();
            out.setBufferSize(bufferSize);
            GZIPOutputStream gz = new GZIPOutputStream(out, bufferSize);
            java.io.InputStream inputStream = new java.io.FileInputStream(file);
            byte[] b = new byte[bufferSize];
            int x=0;
            while ( (x = inputStream.read(b)) != -1) {
                gz.write(b,0,x);
            }
            inputStream.close();

            gz.finish();
            gz.close();

            gz = null;
            b = null;

        }
        else{

          //Write header before sending the file contents.
            setContentLength(fileSize);


          //Send file using Jetty's ByteBuffer API. Note how the file mapped
          //buffers are stored in a ConcurrentHashMap cache to be shared between
          //multiple requests. The call to asReadOnlyBuffer() only creates a
          //position/limit indexes and does not copy the underlying data, which
          //is written directly by the operating system from the file system to
          //the network.
          //https://webtide.com/servlet-3-1-async-io-and-jetty/
            String path = file.getPath();
            ByteBuffer mapped=cache.get(path);
            if (mapped==null){

                try (java.io.RandomAccessFile raf = new java.io.RandomAccessFile(file, "r"))
                {
                    ByteBuffer buf = raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length());
                    mapped=cache.putIfAbsent(path,buf);
                    if (mapped==null) mapped=buf;
                }
            }
            write(mapped.asReadOnlyBuffer());
        }
    }

    private java.util.concurrent.ConcurrentHashMap<String, ByteBuffer> cache = new java.util.concurrent.ConcurrentHashMap<>();

  /** */
    public void write(ByteBuffer content) throws IOException {
        final HttpOutput out = (HttpOutput) response.getOutputStream();
        out.setBufferSize(bufferSize);
        final javax.servlet.AsyncContext async = request.startAsync();
        out.setWriteListener(new javax.servlet.WriteListener(){

            public void onWritePossible() throws IOException{

                while (out.isReady()){

                    if (!content.hasRemaining()){
                        async.complete();
                        return;
                    }

                    out.write(content);
                }
            }

            public void onError(Throwable t) {
                t.printStackTrace();
                request.getServletContext().log("Async Error",t);
                async.complete();
            }
        });
    }


  //**************************************************************************
  //** write
  //**************************************************************************
  /** Used to transfer bytes from an input stream to the response body.
   *  @param inputStream java.io.InputStream
   *  @param compressOutput Specify whether to gzip compress the byte array.
   *  Note that this option will be applied only if "Accept-Encoding" supports
   *  gzip compression. Do not use this option if your bytes are already gzip
   *  compressed.
   */
    public void write(java.io.InputStream inputStream, boolean compressOutput) throws IOException {
        if (inputStream==null) return;


      //Check whether we can/should compress the output
        boolean gzip = false;
        if (compressOutput){
            String acceptEncoding = request.getHeader("Accept-Encoding");
            if (acceptEncoding!=null){
                if (acceptEncoding.toLowerCase().contains("gzip")){
                    gzip = true;
                }
            }
        }


      //Write header before sending the file contents.
        setHeader("Content-Length", null);
        setHeader("Transfer-Encoding", "chunked");
        if (gzip) setHeader("Content-Encoding", "gzip");


      //Write body. Compress as needed.
        java.io.OutputStream out;
        if (gzip) out = new GZIPOutputStream(response.getOutputStream(), bufferSize);
        else out = response.getOutputStream();
        byte[] b = new byte[bufferSize];
        int x=0;
        while ( (x = inputStream.read(b)) != -1) {
            out.write(b,0,x);
        }
        inputStream.close();

        //out.finish();
        out.close();

        out = null;
        b = null;
    }


  //**************************************************************************
  //** getOutputStream
  //**************************************************************************
  /** Returns an output stream for writing the body of an http response.
   *  Automatically encrypts the data if the connection is SSL/TLS encrypted.
   *  Note that by default, if the request header includes a keep-alive
   *  directive, the response header will include a keep-alive response. As
   *  such, you must explicitely set the content length, set the "Connection"
   *  header to "Close", or chunk the output using chunked encoding.
   *  Example:
   <pre>
      //IMPORTANT: Set response headers before getting the output stream!
        response.setContentLength(54674);

      //Get output stream
        java.io.OutputStream outputStream = response.getOutputStream();

      //Transfer bytes from an input stream to the output stream
        byte[] b = new byte[1024];
        int x=0;
        while ( (x = inputStream.read(b)) != -1) {
            outputStream.write(b,0,x);
        }

      //Close the input and output streams
        outputStream.close();
        inputStream.close();
   </pre>
   */
    public ServletOutputStream getOutputStream() throws IOException {
        return new ServletOutputStream(response.getOutputStream());
    }


  //**************************************************************************
  //** getWriter
  //**************************************************************************
  /** Returns a PrintWriter object that can send character text to the client.
   */
    public java.io.PrintWriter getWriter() throws IOException{
        return response.getWriter();
    }


  //**************************************************************************
  //** getHeader
  //**************************************************************************
  /**  Returns the raw HTTP response header.
   */
    public String getHeader(){

      //Update the status code and message as needed
        if (statusCode==null) statusCode = 200;
        if (statusMessage==null) statusMessage = getStatusMessage(statusCode);

        String TransferEncoding = getHeader("Transfer-Encoding");
        boolean chunked = (TransferEncoding!=null ? TransferEncoding.equalsIgnoreCase("chunked") : false);


//      //Update header for range requests
//        if (statusCode<300 && startRange!=null){
//            setStatus(206);
//            if (chunked) setHeader("Transfer-Encoding", null);
//            String contentRange = "bytes " + startRange + "-";
//            Long contentLength = getContentLength();
//            if (contentLength!=null){
//                if (endRange==null){
//                    endRange = contentLength-1;
//                    contentRange += endRange + "/" + contentLength;
//                    setContentLength((endRange+1)-startRange);
//                }
//                else{
//                    if (endRange>=contentLength){
//                        setStatus(416);
//                        contentRange = "bytes */" + contentLength;
//                        setContentLength(0);
//                    }
//                    else{
//                        contentRange += endRange + "/" + contentLength;
//                        setContentLength((endRange+1)-startRange);
//                    }
//                }
//            }
//            else{//Content Length is Unknown
//
//                if (endRange==null){
//                    contentRange += "/*";
//                }
//                else{
//                    contentRange += endRange + "/*";
//                    setContentLength((endRange+1)-startRange);
//                }
//            }
//
//            setHeader("Content-Range", contentRange);
//        }
//        else{
//            startRange = endRange = null;
//        }




      //The http response headers must include a value for "Content-Length"
      //if using a persistant connection (i.e. "Connection: Keep-Alive") and
      //chunkedTransfer is turned off.
        String connType = getHeader("Connection");
        boolean isKeepAlive = (connType!=null ? connType.equalsIgnoreCase("Keep-Alive") : false);

        if (isKeepAlive && getContentLength()==null && chunked==false){
            setHeader("Connection", "Close");
        }


//      //If a new session has been created, add the session id to the response
//        HttpSession session = request.getSession(false);
//        if (session!=null){
//            if (session.isNew())
//                addCookie(new Cookie("JSESSIONID", session.getID()));
//        }



      //Add status line
        StringBuffer header = new StringBuffer();
        header.append("HTTP/1.1 " + statusCode + (statusMessage==null?"": " " + statusMessage) + "\r\n");


      //Add headers
        java.util.Iterator<String> headerNames = response.getHeaderNames().iterator();
        while (headerNames.hasNext()) {
            String name = headerNames.next();
            java.util.Iterator<String> headerValues = response.getHeaders(name).iterator();
            while (headerValues.hasNext()) {
                String value = headerValues.next();
                header.append(name);
                header.append(": ");
                header.append(value);
                header.append("\r\n");
            }
        }


        header.append("\r\n");
        return header.toString();
    }


  //**************************************************************************
  //** toString
  //**************************************************************************
  /**  Returns the raw HTTP response header.
   */
    public String toString(){
        return getHeader();
    }


  //**************************************************************************
  //** getDate
  //**************************************************************************
  /** Used to convert a date to a string (e.g. "Mon, 20 Feb 2012 07:22:20 EST").
   *  This method does not rely on the java.text.SimpleDateFormat for
   *  performance reasons.
   */
    private static String getDate(Calendar cal){

        if (!cal.getTimeZone().equals(tz)){
            cal = (java.util.Calendar) cal.clone();
            cal.setTimeZone(tz);
        }

        StringBuffer str = new StringBuffer(29);
        switch(cal.get(Calendar.DAY_OF_WEEK)){
            case Calendar.MONDAY:    str.append("Mon, "); break;
            case Calendar.TUESDAY:   str.append("Tue, "); break;
            case Calendar.WEDNESDAY: str.append("Wed, "); break;
            case Calendar.THURSDAY:  str.append("Thu, "); break;
            case Calendar.FRIDAY:    str.append("Fri, "); break;
            case Calendar.SATURDAY:  str.append("Sat, "); break;
            case Calendar.SUNDAY:    str.append("Sun, "); break;
        }

        int i = cal.get(Calendar.DAY_OF_MONTH);
        str.append(i<10 ? "0"+i : i);

        switch (cal.get(Calendar.MONTH)) {
            case Calendar.JANUARY:   str.append(" Jan "); break;
            case Calendar.FEBRUARY:  str.append(" Feb "); break;
            case Calendar.MARCH:     str.append(" Mar "); break;
            case Calendar.APRIL:     str.append(" Apr "); break;
            case Calendar.MAY:       str.append(" May "); break;
            case Calendar.JUNE:      str.append(" Jun "); break;
            case Calendar.JULY:      str.append(" Jul "); break;
            case Calendar.AUGUST:    str.append(" Aug "); break;
            case Calendar.SEPTEMBER: str.append(" Sep "); break;
            case Calendar.OCTOBER:   str.append(" Oct "); break;
            case Calendar.NOVEMBER:  str.append(" Nov "); break;
            case Calendar.DECEMBER:  str.append(" Dec "); break;
        }

        str.append(cal.get(Calendar.YEAR));
        str.append(" ");

        i = cal.get(Calendar.HOUR_OF_DAY);
        str.append(i<10 ? "0"+i+":" : i+":");

        i = cal.get(Calendar.MINUTE);
        str.append(i<10 ? "0"+i+":" : i+":");

        i = cal.get(Calendar.SECOND);
        str.append(i<10 ? "0"+i+" " : i+" ");

        str.append(z);
        return str.toString();

        //new java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
        //return f.format(date);
    }


    private String getDate(long milliseconds){
        java.util.Calendar cal = java.util.Calendar.getInstance();
        cal.setTimeInMillis(milliseconds);
        return getDate(cal);
    }


  //**************************************************************************
  //** reset
  //**************************************************************************
  /** Clears the status code and headers. This method is called automatically
   *  after each new http request to free up server resources. You do not need
   *  to call this method explicitly from your application.
   */
    public void reset(){
        response.reset();
    }


  //**************************************************************************
  //** flushBuffer
  //**************************************************************************
  /** Forces any content in the buffer to be written to the client. A call to
   *  this method automatically commits the response, meaning the status code
   *  and headers will be written. This method is called automatically after
   *  each http request to free up server resources. You do not need to call
   *  this method explicitly from your application.
   */
    public void flushBuffer() throws java.io.IOException {
        response.flushBuffer();
    }


  //**************************************************************************
  //** resetBuffer
  //**************************************************************************
  /** According to spec, this method is supposed to clear the content buffer.
   *  However, this implementation doesn't use a content buffer. All content
   *  is written immediately.
   */
    public void resetBuffer(){
        response.resetBuffer();
    }


  //**************************************************************************
  //** isCommitted
  //**************************************************************************
  /** Returns a boolean indicating if the response has been committed. A
   *  committed response has already had its status code and headers written.
   */
    public boolean isCommitted(){
        return response.isCommitted();
    }


  //**************************************************************************
  //** encodeURL
  //**************************************************************************
  /** According to spec, this method is supposed to encode the specified URL
   *  by including the session ID in it, or, if encoding is not needed,
   *  returns the URL unchanged. This is important for browsers that don't
   *  support cookies.  In our case, sessions are maintained using cookies so
   *  we return the URL unchanged.
   */
    public String encodeURL(String url){
        return url;
    }


  //**************************************************************************
  //** encodeRedirectURL
  //**************************************************************************
  /** According to spec, this method is supposed to encode the specified URL
   *  for use in the sendRedirect method or, if encoding is not needed,
   *  returns the URL unchanged. The implementation of this method includes
   *  logic to determine whether the session ID needs to be encoded in the URL.
   *  This is important for browsers that don't support cookies.  In our case,
   *  sessions are maintained using cookies so we return the URL unchanged.
   */
    public String encodeRedirectURL(String url){
        return url;
    }


  //**************************************************************************
  //** encodeUrl
  //**************************************************************************
  /** @deprecated Use encodeURL(String url) instead
   */
    @Deprecated
    public String encodeUrl(String url){
        return encodeURL(url);
    }

  //**************************************************************************
  //** encodeRedirectUrl
  //**************************************************************************
  /** @deprecated Use encodeRedirectURL(String url) instead
   */
    @Deprecated
    public String encodeRedirectUrl(String url){
        return url;
    }


  //**************************************************************************
  //** Server status codes; see RFC 2068.
  //**************************************************************************

  /** Status code (100) indicating the client can continue. */
    public static final int SC_CONTINUE = 100;

  /** Status code (101) indicating the server is switching protocols according
   *  to Upgrade header. */
    public static final int SC_SWITCHING_PROTOCOLS = 101;

  /** Status code (200) indicating the request succeeded normally. */
    public static final int SC_OK = 200;

  /** Status code (201) indicating the request succeeded and created a new
   *  resource on the server. */
    public static final int SC_CREATED = 201;

  /** Status code (202) indicating that a request was accepted for processing,
   * but was not completed. */
    public static final int SC_ACCEPTED = 202;

  /** Status code (203) indicating that the meta information presented by the
   *  client did not originate from the server. */
    public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;

  /** Status code (204) indicating that the request succeeded but that there
   *  was no new information to return. */
    public static final int SC_NO_CONTENT = 204;

  /** Status code (205) indicating that the agent <em>SHOULD</em> reset
   *  the document view which caused the request to be sent. */
    public static final int SC_RESET_CONTENT = 205;

  /** Status code (206) indicating that the server has fulfilled the partial
   *  GET request for the resource. */
    public static final int SC_PARTIAL_CONTENT = 206;

  /** Status code (300) indicating that the requested resource corresponds to
   *  any one of a set of representations, each with its own specific location.
   */
    public static final int SC_MULTIPLE_CHOICES = 300;

  /** Status code (301) indicating that the resource has permanently moved to
   *  a new location, and that future references should use a new URI with
   *  their requests. */
    public static final int SC_MOVED_PERMANENTLY = 301;

  /** Status code (302) indicating that the resource has temporarily moved to
   *  another location, but that future references should still use the
   * original URI to access the resource. This definition is being retained for
   * backwards compatibility. SC_FOUND is now the preferred definition. */
    public static final int SC_MOVED_TEMPORARILY = 302;

  /** Status code (302) indicating that the resource reside temporarily under
   *  a different URI. Since the redirection might be altered on occasion, the
   *  client should continue to use the Request-URI for future requests.
   * (HTTP/1.1) To represent the status code (302), it is recommended to use
   *  this variable. */
    public static final int SC_FOUND = 302;

  /** Status code (303) indicating that the response to the request can be
   *  found under a different URI. */
    public static final int SC_SEE_OTHER = 303;

  /** Status code (304) indicating that a conditional GET operation found that
   * the resource was available and not modified. */
    public static final int SC_NOT_MODIFIED = 304;

  /** Status code (305) indicating that the requested resource <em>MUST</em>
   *  be accessed through the proxy given by the Location field. */
    public static final int SC_USE_PROXY = 305;

   /** Status code (307) indicating that the requested resource resides
    *  temporarily under a different URI. The temporary URI <em>SHOULD</em> be
    *  given by the <code><em>Location</em></code> field in the response. */
     public static final int SC_TEMPORARY_REDIRECT = 307;

  /** Status code (400) indicating the request sent by the client was
   *  syntactically incorrect. */
    public static final int SC_BAD_REQUEST = 400;

  /** Status code (401) indicating that the request requires HTTP
   *  authentication. */
    public static final int SC_UNAUTHORIZED = 401;

  /** Status code (402) reserved for future use. */
    public static final int SC_PAYMENT_REQUIRED = 402;

  /** Status code (403) indicating the server understood the request but
   *  refused to fulfill it. */
    public static final int SC_FORBIDDEN = 403;

  /** Status code (404) indicating that the requested resource is not
   *  available. */
    public static final int SC_NOT_FOUND = 404;

  /** Status code (405) indicating that the method specified in the
   *  <code><em>Request-Line</em></code> is not allowed for the resource
   *  identified by the <code><em>Request-URI</em></code>. */
    public static final int SC_METHOD_NOT_ALLOWED = 405;

  /** Status code (406) indicating that the resource identified by the request
   *  is only capable of generating response entities which have content
   *  characteristics not acceptable according to the accept headers sent in
   *  the request. */
    public static final int SC_NOT_ACCEPTABLE = 406;

  /** Status code (407) indicating that the client <em>MUST</em> first
   *  authenticate itself with the proxy. */
    public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;

  /** Status code (408) indicating that the client did not produce a request
   *  within the time that the server was prepared to wait. */
    public static final int SC_REQUEST_TIMEOUT = 408;

  /** Status code (409) indicating that the request could not be completed due
   *  to a conflict with the current state of the resource. */
    public static final int SC_CONFLICT = 409;

  /** Status code (410) indicating that the resource is no longer available at
   *  the server and no forwarding address is known. This condition
   *  <em>SHOULD</em> be considered permanent. */
    public static final int SC_GONE = 410;

  /** Status code (411) indicating that the request cannot be handled without
   *  a defined <code><em>Content-Length</em></code>. */
    public static final int SC_LENGTH_REQUIRED = 411;

  /** Status code (412) indicating that the precondition given in one or more
   *  of the request-header fields evaluated to false when it was tested on
   * the server. */
    public static final int SC_PRECONDITION_FAILED = 412;

  /** Status code (413) indicating that the server is refusing to process the
   * request because the request entity is larger than the server is willing
   * or able to process. */
    public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;

  /** Status code (414) indicating that the server is refusing to service the
   * request because the <code><em>Request-URI</em></code> is longer than the
   * server is willing to interpret. */
    public static final int SC_REQUEST_URI_TOO_LONG = 414;

  /** Status code (415) indicating that the server is refusing to service the
   * request because the entity of the request is in a format not supported by
   * the requested resource for the requested method. */
    public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;

  /** Status code (416) indicating that the server cannot serve the requested
   *  byte range. */
    public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;

  /** Status code (417) indicating that the server could not meet the
   * expectation given in the Expect request header. */
    public static final int SC_EXPECTATION_FAILED = 417;

  /** Status code (500) indicating an error inside the HTTP server which
   *  prevented it from fulfilling the request. */
    public static final int SC_INTERNAL_SERVER_ERROR = 500;

  /** Status code (501) indicating the HTTP server does not support the
   *  functionality needed to fulfill the request. */
    public static final int SC_NOT_IMPLEMENTED = 501;

  /** Status code (502) indicating that the HTTP server received an invalid
   *  response from a server it consulted when acting as a proxy or gateway. */
    public static final int SC_BAD_GATEWAY = 502;

  /** Status code (503) indicating that the HTTP server is temporarily
   *  overloaded, and unable to handle the request. */
    public static final int SC_SERVICE_UNAVAILABLE = 503;

  /** Status code (504) indicating that the server did not receive a timely
   * response from the upstream server while acting as a gateway or proxy. */
    public static final int SC_GATEWAY_TIMEOUT = 504;

  /** Status code (505) indicating that the server does not support or refuses
   * to support the HTTP protocol version that was used in the request message.
   */
    public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
}