/*
* JLibs: Common Utilities for Java
* Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package jlibs.nio.http;
import jlibs.core.lang.NotImplementedException;
import jlibs.nio.Reactor;
import jlibs.nio.Writable;
import jlibs.nio.http.msg.*;
import jlibs.nio.http.util.Encoding;
import jlibs.nio.listeners.Task;
import jlibs.nio.util.Buffers;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.List;
import static java.nio.channels.SelectionKey.OP_WRITE;
import static jlibs.nio.Debugger.HTTP;
import static jlibs.nio.Debugger.println;
import static jlibs.nio.http.WriteMessage.State.*;
import static jlibs.nio.http.util.USAscii.*;
/**
* @author Santhosh Kumar Tekuri
*/
public final class WriteMessage extends Task{
public WriteMessage(){
super(OP_WRITE);
}
enum State{
WRITE_BUFFER, WRITE_HEAD, FLUSH_HEAD, PREPARE_BUFFERS, WRITE_BUFFERS,
WRITE_PAYLOAD,
CLOSE_OUTPUTS
}
private State state;
@Override
protected boolean process(int readyOp) throws IOException{
while(true){
switch(state){
case FLUSH_HEAD:
if(!send(buffer))
return false;
state = PREPARE_BUFFERS;
return true;
case WRITE_BUFFER:
if(write(buffer)){
if(header==null){
state = PREPARE_BUFFERS;
break;
}else{
buffer.clear();
state = WRITE_HEAD;
}
}else
return false;
case WRITE_HEAD:
while(header!=null){
if(writeName){
AsciiString name = header.getName();
do{
index = name.putInto(buffer, index);
if(buffer.remaining()<2){
buffer.flip();
if(write(buffer))
buffer.clear();
else{
state = WRITE_BUFFER;
return false;
}
}
}while(index!=name.text.length());
buffer.put(COLON);
buffer.put(SP);
writeName = false;
index = 0;
}
String value = header.getValue();
do{
int toIndex = Math.min(value.length(), index+buffer.remaining());
while(index<toIndex)
buffer.put((byte)value.charAt(index++));
if(buffer.remaining()<4){
buffer.flip();
if(write(buffer))
buffer.clear();
else{
state = WRITE_BUFFER;
return false;
}
}
}while(index!=value.length());
buffer.put(CR);
buffer.put(LF);
writeName = true;
index = 0;
header = header.next();
}
buffer.put(CR);
buffer.put(LF);
buffer.flip();
if(sendPayload)
state = PREPARE_BUFFERS;
else{
state = FLUSH_HEAD;
break;
}
case PREPARE_BUFFERS:
if(buffers==null){
if(buffer.hasRemaining()){
state = WRITE_BUFFER;
break;
}else{
state = WRITE_PAYLOAD;
break;
}
}else{
if(buffer.hasRemaining()){
ByteBuffer array[] = new ByteBuffer[1+buffers.length];
array[0] = buffer;
System.arraycopy(buffers.array, buffers.offset, array, 1, buffers.length);
buffers = new Buffers(array, 0, array.length);
prepareFlush(buffers, !retain);
if(!retain)
buffer = null;
}else
prepareFlush(buffers, !retain);
state = WRITE_BUFFERS;
}
case WRITE_BUFFERS:
if(!flushBuffers())
return false;
state = WRITE_PAYLOAD;
case WRITE_PAYLOAD:
if(writePayload==null)
state = CLOSE_OUTPUTS;
else{
setChild(writePayload);
return true;
}
case CLOSE_OUTPUTS:
if(error!=null){
if(error instanceof IOException)
throw (IOException)error;
if(error instanceof RuntimeException)
throw (RuntimeException)error;
throw (Error)error;
}
return closeOutputs();
}
}
}
private ByteBuffer buffer;
private Header header;
private boolean writeName;
private int index;
private boolean retain;
private Buffers buffers;
private boolean sendPayload;
private WritePayload writePayload;
public void reset(Message message, ByteBuffer continue100Buffer, boolean sendPayload){
if(buffer==null)
buffer = Reactor.current().allocator.allocate();
else
buffer.clear();
this.sendPayload = sendPayload;
writeName = true;
index = 0;
retain = false;
buffers = null;
writePayload = null;
error = null;
Payload payload = message.getPayload();
message.headers.set(Message.CONTENT_TYPE, payload.contentType);
if(payload.getContentLength()==0){
message.headers.remove(Message.CONTENT_ENCODING);
boolean removeCL = false;
if(message instanceof Request){
Request request = (Request)message;
if(!request.method.requestPayloadAllowed)
removeCL = true;
}else{
Response response = (Response)message;
if(response.status.payloadNotAllowed)
removeCL = true;
}
if(removeCL){
message.headers.remove(Message.CONTENT_LENGTH);
message.headers.remove(Message.TRANSFER_ENCODING);
}else
message.setContentLength(0);
}else if(payload instanceof EncodablePayload){
EncodablePayload encodablePayload = (EncodablePayload)payload;
buffers = new Buffers();
OutputStream os = buffers;
try{
List<Encoding> encodings = message.getContentEncodings();
while(!encodings.isEmpty())
os = encodings.remove(encodings.size()-1).wrap(os);
encodablePayload.writeTo(os);
os.close();
}catch(IOException ex){
throw Status.INTERNAL_SERVER_ERROR.with(ex);
}
message.setContentLength(buffers.remaining());
}else if(payload instanceof SocketPayload){
SocketPayload socketPayload = (SocketPayload)payload;
if(socketPayload.in!=null && socketPayload.in.isOpen()){
writePayload = new WriteSocketPayload(socketPayload);
long contentLength = socketPayload.getContentLength();
List<Encoding> encodings = socketPayload.encodings;
if(encodings!=null && !encodings.isEmpty()){
message.setContentEncodings(encodings);
if(contentLength!=-1){
buffers = socketPayload.buffers;
retain = socketPayload.retain;
((WriteSocketPayload)writePayload).ignoreBuffers = true;
}
}else{
writePayload.encodings = message.getContentEncodings();
if(writePayload.encodings.isEmpty()){
if(contentLength!=-1){
buffers = socketPayload.buffers;
retain = socketPayload.retain;
((WriteSocketPayload)writePayload).ignoreBuffers = true;
}
}else
contentLength = -1;
}
if(contentLength==-1){
writePayload.chunked = true;
message.setChunked();
}else
message.setContentLength(contentLength);
}else if(socketPayload.buffers==null){
message.setContentEncodings(null);
message.setContentLength(-1);
}else{
buffers = socketPayload.buffers;
retain = socketPayload.retain;
message.setContentEncodings(socketPayload.encodings);
message.setContentLength(buffers.remaining());
}
}else if(payload instanceof FilePayload){
FilePayload filePayload = (FilePayload)payload;
writePayload = new WriteFilePayload(filePayload);
writePayload.encodings = message.getContentEncodings();
if(writePayload.encodings.isEmpty())
message.setContentLength(filePayload.getContentLength());
else{
writePayload.chunked = true;
message.setChunked();
}
}else
throw new NotImplementedException("write"+payload.getClass().getSimpleName());
if(HTTP){
println("writeMessage{");
println(message);
println("}");
}
if(continue100Buffer!=null)
buffer.put(continue100Buffer);
message.putLineInto(buffer);
header = message.headers.getFirst();
if(header==null){
buffer.put(CR);
buffer.put(LF);
buffer.flip();
state = PREPARE_BUFFERS;
}else
state = WRITE_HEAD;
}
private Throwable error;
@Override
protected int childTaskFinished(Task childTask, Throwable thr){
error = thr;
out = ((Writable)out.channel()).out();
out.channel().makeActive();
state = CLOSE_OUTPUTS;
if(HTTP)
println("state = "+state);
return OP_WRITE;
}
public void dispose(){
if(buffer!=null){
Reactor.current().allocator.free(buffer);
buffer = null;
}
}
@Override
public String toString(){
return "WriteMessage";
}
}