package com.koushikdutta.async.http.server;
import android.text.TextUtils;
import com.koushikdutta.async.*;
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;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
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;
BufferedDataSink mChunker;
void initFirstWrite() {
if (mHasWritten)
return;
assert null != mRawHeaders.getStatusLine();
if (mContentLength < 0) {
mRawHeaders.set("Transfer-Encoding", "Chunked");
mChunker = new ChunkedOutputFilter(mSink);
}
else {
mChunker = mSink;
}
writeHead();
mSink.setMaxBuffer(0);
mHasWritten = true;
}
private void writeInternal(ByteBufferList bb) {
assert !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") && !mHasWritten) {
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 !mHeadWritten;
mHeadWritten = true;
mSink.write(ByteBuffer.wrap(mRawHeaders.toHeaderString().getBytes()));
}
@Override
public void setContentType(String contentType) {
assert !mHeadWritten;
mRawHeaders.set("Content-Type", contentType);
}
public void send(String contentType, String string) {
try {
if (mRawHeaders.getStatusLine() == null)
responseCode(200);
assert 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 false;
}
}
boolean mEnded;
protected void onEnd() {
mEnded = true;
}
protected void report(Exception e) {
}
@Override
public void send(String string) {
responseCode(200);
send("text/html; charset=utf8", string);
}
@Override
public void send(JSONObject json) {
send("application/json; charset=utf8", json.toString());
}
public void sendFile(File file) {
int start = 0;
int end = (int)file.length();
String range = mRequest.getHeaders().getHeaders().get("Range");
if (range != null) {
String[] parts = range.split("=");
if (parts.length != 2 || !"bytes".equals(parts[0])) {
// Requested range not satisfiable
responseCode(416);
end();
return;
}
parts = parts[1].split("-");
try {
if (parts.length > 2)
throw new Exception();
if (!TextUtils.isEmpty(parts[0]))
start = Integer.parseInt(parts[0]);
if (parts.length == 2 && !TextUtils.isEmpty(parts[1]))
end = Integer.parseInt(parts[1]);
else if (start != 0)
end = (int)file.length();
else
end = Math.min((int)file.length(), start + 50000);
responseCode(206);
getHeaders().getHeaders().set("Content-Range", String.format("bytes %d-%d/%d", start, end - 1, file.length()));
}
catch (Exception e) {
responseCode(416);
end();
return;
}
}
try {
FileInputStream fin = new FileInputStream(file);
if (start != fin.skip(start))
throw new Exception();
mRawHeaders.set("Content-Type", AsyncHttpServer.getContentType(file.getAbsolutePath()));
mContentLength = end - start;
mRawHeaders.set("Content-Length", "" + mContentLength);
if (getHeaders().getHeaders().getStatusLine() == null)
responseCode(200);
Util.pump(fin, end - start, this, new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
end();
}
});
}
catch (Exception 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();
}
}