package com.koushikdutta.async.http.server; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import junit.framework.Assert; import org.json.JSONObject; import com.koushikdutta.async.AsyncServer; import com.koushikdutta.async.AsyncSocket; import com.koushikdutta.async.BufferedDataSink; import com.koushikdutta.async.ByteBufferList; import com.koushikdutta.async.Util; import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.callback.WritableCallback; import com.koushikdutta.async.http.filter.ChunkedOutputFilter; import com.koushikdutta.async.http.libcore.RawHeaders; import com.koushikdutta.async.http.libcore.ResponseHeaders; public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse { private RawHeaders mRawHeaders = new RawHeaders(); private int mContentLength = -1; private ResponseHeaders mHeaders = new ResponseHeaders(null, mRawHeaders); @Override public ResponseHeaders getHeaders() { return mHeaders; } public AsyncSocket getSocket() { return mSocket; } AsyncSocket mSocket; BufferedDataSink mSink; AsyncHttpServerRequestImpl mRequest; AsyncHttpServerResponseImpl(AsyncSocket socket, AsyncHttpServerRequestImpl req) { mSocket = socket; mSink = new BufferedDataSink(socket); mRequest = req; mRawHeaders.set("Connection", "Keep-Alive"); } @Override public void write(ByteBuffer bb) { if (bb.remaining() == 0) return; writeInternal(bb); } private void writeInternal(ByteBuffer bb) { initFirstWrite(); mChunker.write(bb); } boolean mHasWritten = false; ChunkedOutputFilter mChunker; void initFirstWrite() { if (mHasWritten) return; Assert.assertTrue(mContentLength < 0); Assert.assertNotNull(mRawHeaders.getStatusLine()); mRawHeaders.set("Transfer-Encoding", "Chunked"); writeHead(); mSink.setMaxBuffer(0); mHasWritten = true; mChunker = new ChunkedOutputFilter(mSink); } private void writeInternal(ByteBufferList bb) { Assert.assertTrue(!mEnded); initFirstWrite(); mChunker.write(bb); } @Override public void write(ByteBufferList bb) { if (bb.remaining() == 0) return; writeInternal(bb); } @Override public void setWriteableCallback(WritableCallback handler) { initFirstWrite(); mChunker.setWriteableCallback(handler); } @Override public WritableCallback getWriteableCallback() { initFirstWrite(); return mChunker.getWriteableCallback(); } @Override public void end() { if (null == mRawHeaders.get("Transfer-Encoding")) { send("text/html", ""); onEnd(); return; } initFirstWrite(); mChunker.setMaxBuffer(Integer.MAX_VALUE); mChunker.write(new ByteBufferList()); onEnd(); } private boolean mHeadWritten = false; @Override public void writeHead() { Assert.assertFalse(mHeadWritten); mHeadWritten = true; mSink.write(ByteBuffer.wrap(mRawHeaders.toHeaderString().getBytes())); } @Override public void setContentType(String contentType) { Assert.assertFalse(mHeadWritten); mRawHeaders.set("Content-Type", contentType); } public void send(String contentType, String string) { try { if (mRawHeaders.getStatusLine() == null) responseCode(200); Assert.assertTrue(mContentLength < 0); byte[] bytes = string.getBytes("UTF-8"); mContentLength = bytes.length; mRawHeaders.set("Content-Length", Integer.toString(bytes.length)); mRawHeaders.set("Content-Type", contentType); writeHead(); mSink.write(ByteBuffer.wrap(string.getBytes())); onEnd(); } catch (UnsupportedEncodingException e) { Assert.fail(); } } boolean mEnded; protected void onEnd() { mEnded = true; } protected void report(Exception e) { } @Override public void send(String string) { responseCode(200); send("text/html", string); } @Override public void send(JSONObject json) { send("application/json", json.toString()); } public void sendFile(File file) { try { FileInputStream fin = new FileInputStream(file); mRawHeaders.set("Content-Type", AsyncHttpServer.getContentType(file.getAbsolutePath())); responseCode(200); Util.pump(fin, this, new CompletedCallback() { @Override public void onCompleted(Exception ex) { end(); } }); } catch (FileNotFoundException e) { responseCode(404); end(); } } @Override public void responseCode(int code) { String status = AsyncHttpServer.getResponseCodeDescription(code); mRawHeaders.setStatusLine(String.format("HTTP/1.1 %d %s", code, status)); } @Override public void redirect(String location) { responseCode(302); mRawHeaders.set("Location", location); end(); } @Override public void onCompleted(Exception ex) { if (ex != null) { ex.printStackTrace(); } end(); } @Override public boolean isOpen() { return mSink.isOpen(); } @Override public void close() { end(); // if we're using the chunker, close that. // there may be data pending. That will eventually call // the close callback in the underlying mSink if (mChunker != null) mChunker.close(); else mSink.close(); } @Override public void setClosedCallback(CompletedCallback handler) { mSink.setClosedCallback(handler); } @Override public CompletedCallback getClosedCallback() { return mSink.getClosedCallback(); } @Override public AsyncServer getServer() { return mSocket.getServer(); } }