org.eclipse.jetty.ajp.Ajp13Generator Maven / Gradle / Ivy
The newest version!
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.ajp;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import org.eclipse.jetty.http.AbstractGenerator;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpTokens;
import org.eclipse.jetty.http.HttpVersions;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
*
*
*/
public class Ajp13Generator extends AbstractGenerator
{
private static final Logger LOG = Log.getLogger(Ajp13Generator.class);
private static HashMap __headerHash = new HashMap();
static
{
byte[] xA001 =
{ (byte)0xA0, (byte)0x01 };
byte[] xA002 =
{ (byte)0xA0, (byte)0x02 };
byte[] xA003 =
{ (byte)0xA0, (byte)0x03 };
byte[] xA004 =
{ (byte)0xA0, (byte)0x04 };
byte[] xA005 =
{ (byte)0xA0, (byte)0x05 };
byte[] xA006 =
{ (byte)0xA0, (byte)0x06 };
byte[] xA007 =
{ (byte)0xA0, (byte)0x07 };
byte[] xA008 =
{ (byte)0xA0, (byte)0x08 };
byte[] xA009 =
{ (byte)0xA0, (byte)0x09 };
byte[] xA00A =
{ (byte)0xA0, (byte)0x0A };
byte[] xA00B =
{ (byte)0xA0, (byte)0x0B };
__headerHash.put("Content-Type",xA001);
__headerHash.put("Content-Language",xA002);
__headerHash.put("Content-Length",xA003);
__headerHash.put("Date",xA004);
__headerHash.put("Last-Modified",xA005);
__headerHash.put("Location",xA006);
__headerHash.put("Set-Cookie",xA007);
__headerHash.put("Set-Cookie2",xA008);
__headerHash.put("Servlet-Engine",xA009);
__headerHash.put("Status",xA00A);
__headerHash.put("WWW-Authenticate",xA00B);
}
// A, B ajp response header
// 0, 1 ajp int 1 packet length
// 9 CPONG response Code
private static final byte[] AJP13_CPONG_RESPONSE =
{ 'A', 'B', 0, 1, 9 };
private static final byte[] AJP13_END_RESPONSE =
{ 'A', 'B', 0, 2, 5, 1 };
// AB ajp respose
// 0, 3 int = 3 packets in length
// 6, send signal to get more data
// 31, -7 byte values for int 8185 = (8 * 1024) - 7 MAX_DATA
private static final byte[] AJP13_MORE_CONTENT =
{ 'A', 'B', 0, 3, 6, 31, -7 };
private static String SERVER = "Server: Jetty(7.x.x)";
public static void setServerVersion(String version)
{
SERVER = "Jetty(" + version + ")";
}
/* ------------------------------------------------------------ */
private boolean _expectMore = false;
private boolean _needMore = false;
private boolean _needEOC = false;
private boolean _bufferPrepared = false;
/* ------------------------------------------------------------ */
public Ajp13Generator(Buffers buffers, EndPoint io)
{
super(buffers,io);
}
/* ------------------------------------------------------------ */
@Override
public boolean isRequest()
{
return false;
}
/* ------------------------------------------------------------ */
@Override
public boolean isResponse()
{
return true;
}
/* ------------------------------------------------------------ */
@Override
public void reset()
{
super.reset();
_needEOC = false;
_needMore = false;
_expectMore = false;
_bufferPrepared = false;
_last = false;
_state = STATE_HEADER;
_status = 0;
_version = HttpVersions.HTTP_1_1_ORDINAL;
_reason = null;
_method = null;
_uri = null;
_contentWritten = 0;
_contentLength = HttpTokens.UNKNOWN_CONTENT;
_last = false;
_head = false;
_noContent = false;
_persistent = true;
_header = null; // Buffer for HTTP header (and maybe small _content)
_buffer = null; // Buffer for copy of passed _content
_content = null; // Buffer passed to addContent
}
/* ------------------------------------------------------------ */
@Override
public int getContentBufferSize()
{
try
{
initContent();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
return super.getContentBufferSize() - 7;
}
/* ------------------------------------------------------------ */
@Override
public void increaseContentBufferSize(int contentBufferSize)
{
// Not supported with AJP
}
/* ------------------------------------------------------------ */
/**
* Add content.
*
* @param content
* @param last
* @throws IllegalArgumentException
* if content
is {@link Buffer#isImmutable immutable}.
* @throws IllegalStateException
* If the request is not expecting any more content, or if the buffers are full and cannot be flushed.
* @throws IOException
* if there is a problem flushing the buffers.
*/
public void addContent(Buffer content, boolean last) throws IOException
{
if (_noContent)
{
content.clear();
return;
}
if (content.isImmutable())
throw new IllegalArgumentException("immutable");
if (_last || _state == STATE_END)
{
LOG.debug("Ignoring extra content {}",content);
content.clear();
return;
}
_last = last;
if (!_endp.isOpen())
{
_state = STATE_END;
return;
}
// Handle any unfinished business?
if (_content != null && _content.length() > 0)
{
flushBuffer();
if (_content != null && _content.length() > 0)
throw new IllegalStateException("FULL");
}
_content = content;
_contentWritten += content.length();
// Handle the _content
if (_head)
{
content.clear();
_content = null;
}
else
{
// Yes - so we better check we have a buffer
initContent();
// Copy _content to buffer;
int len = 0;
len = _buffer.put(_content);
// make sure there is space for a trailing null
if (len > 0 && _buffer.space() == 0)
{
len--;
_buffer.setPutIndex(_buffer.putIndex() - 1);
}
_content.skip(len);
if (_content.length() == 0)
_content = null;
}
}
/* ------------------------------------------------------------ */
/**
* Prepare buffer for unchecked writes. Prepare the generator buffer to receive unchecked writes
*
* @return the available space in the buffer.
* @throws IOException
*/
@Override
public int prepareUncheckedAddContent() throws IOException
{
if (_noContent)
return -1;
if (_last || _state == STATE_END)
throw new IllegalStateException("Closed");
if (!_endp.isOpen())
{
_state = STATE_END;
return -1;
}
// Handle any unfinished business?
Buffer content = _content;
if (content != null && content.length() > 0)
{
flushBuffer();
if (content != null && content.length() > 0)
throw new IllegalStateException("FULL");
}
// we better check we have a buffer
initContent();
_contentWritten -= _buffer.length();
// Handle the _content
if (_head)
return Integer.MAX_VALUE;
return _buffer.space() - 1;
}
/* ------------------------------------------------------------ */
@Override
public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
{
if (_state != STATE_HEADER)
return;
if (_last && !allContentAdded)
throw new IllegalStateException("last?");
_last = _last | allContentAdded;
boolean has_server = false;
if (_persistent == null)
_persistent = (_version > HttpVersions.HTTP_1_0_ORDINAL);
// get a header buffer
if (_header == null)
_header = _buffers.getHeader();
Buffer tmpbuf = _buffer;
_buffer = _header;
try
{
// start the header
_buffer.put((byte)'A');
_buffer.put((byte)'B');
addInt(0);
_buffer.put((byte)0x4);
addInt(_status);
if (_reason == null)
_reason = HttpGenerator.getReasonBuffer(_status);
if (_reason == null)
_reason = new ByteArrayBuffer(Integer.toString(_status));
addBuffer(_reason);
if (_status == 100 || _status == 204 || _status == 304)
{
_noContent = true;
_content = null;
}
// allocate 2 bytes for number of headers
int field_index = _buffer.putIndex();
addInt(0);
int num_fields = 0;
if (fields != null)
{
// Add headers
int s = fields.size();
for (int f = 0; f < s; f++)
{
HttpFields.Field field = fields.getField(f);
if (field == null)
continue;
num_fields++;
byte[] codes = (byte[])__headerHash.get(field.getName());
if (codes != null)
{
_buffer.put(codes);
}
else
{
addString(field.getName());
}
addString(field.getValue());
}
}
if (!has_server && _status > 100 && getSendServerVersion())
{
num_fields++;
addString("Server");
addString(SERVER);
}
// TODO Add content length if last content known.
// insert the number of headers
int tmp = _buffer.putIndex();
_buffer.setPutIndex(field_index);
addInt(num_fields);
_buffer.setPutIndex(tmp);
// get the payload size ( - 4 bytes for the ajp header)
// excluding the
// ajp header
int payloadSize = _buffer.length() - 4;
// insert the total packet size on 2nd and 3rd byte that
// was previously
// allocated
addInt(2,payloadSize);
}
finally
{
_buffer = tmpbuf;
}
_state = STATE_CONTENT;
}
/* ------------------------------------------------------------ */
/**
* Complete the message.
*
* @throws IOException
*/
@Override
public void complete() throws IOException
{
if (_state == STATE_END)
return;
super.complete();
if (_state < STATE_FLUSHING)
{
_state = STATE_FLUSHING;
_needEOC = true;
}
flushBuffer();
}
/* ------------------------------------------------------------ */
@Override
public int flushBuffer() throws IOException
{
try
{
if (_state == STATE_HEADER && !_expectMore)
throw new IllegalStateException("State==HEADER");
prepareBuffers();
if (_endp == null)
{
// TODO - probably still needed!
// if (_rneedMore && _buffe != null)
// {
// if(!_hasSentEOC)
// _buffer.put(AJP13_MORE_CONTENT);
// }
if (!_expectMore && _needEOC && _buffer != null)
{
_buffer.put(AJP13_END_RESPONSE);
}
_needEOC = false;
return 0;
}
// Keep flushing while there is something to flush
// (except break below)
int total = 0;
long last_len = -1;
Flushing: while (true)
{
int len = -1;
int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0);
switch (to_flush)
{
case 7:
throw new IllegalStateException(); // should
// never
// happen!
case 6:
len = _endp.flush(_header,_buffer,null);
break;
case 5:
throw new IllegalStateException(); // should
// never
// happen!
case 4:
len = _endp.flush(_header);
break;
case 3:
throw new IllegalStateException(); // should
// never
// happen!
case 2:
len = _endp.flush(_buffer);
break;
case 1:
throw new IllegalStateException(); // should
// never
// happen!
case 0:
{
// Nothing more we can write now.
if (_header != null)
_header.clear();
_bufferPrepared = false;
if (_buffer != null)
{
_buffer.clear();
// reserve some space for the
// header
_buffer.setPutIndex(7);
_buffer.setGetIndex(7);
// Special case handling for
// small left over buffer from
// an addContent that caused a
// buffer flush.
if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
{
_buffer.put(_content);
_content.clear();
_content = null;
break Flushing;
}
}
// Are we completely finished for now?
if (!_expectMore && !_needEOC && (_content == null || _content.length() == 0))
{
if (_state == STATE_FLUSHING)
_state = STATE_END;
// if (_state == STATE_END)
// {
// _endp.close();
// }
//
break Flushing;
}
// Try to prepare more to write.
prepareBuffers();
}
}
// If we failed to flush anything twice in a row
// break
if (len <= 0)
{
if (last_len <= 0)
break Flushing;
break;
}
last_len = len;
total += len;
}
return total;
}
catch (IOException e)
{
LOG.ignore(e);
throw (e instanceof EofException)?e:new EofException(e);
}
}
/* ------------------------------------------------------------ */
private void prepareBuffers()
{
if (!_bufferPrepared)
{
// Refill buffer if possible
if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
{
int len = _buffer.put(_content);
// Make sure there is space for a trailing null
if (len > 0 && _buffer.space() == 0)
{
len--;
_buffer.setPutIndex(_buffer.putIndex() - 1);
}
_content.skip(len);
if (_content.length() == 0)
_content = null;
if (_buffer.length() == 0)
{
_content = null;
}
}
// add header if needed
if (_buffer != null)
{
int payloadSize = _buffer.length();
// 4 bytes for the ajp header
// 1 byte for response type
// 2 bytes for the response size
// 1 byte because we count from zero??
if (payloadSize > 0)
{
_bufferPrepared = true;
_buffer.put((byte)0);
int put = _buffer.putIndex();
_buffer.setGetIndex(0);
_buffer.setPutIndex(0);
_buffer.put((byte)'A');
_buffer.put((byte)'B');
addInt(payloadSize + 4);
_buffer.put((byte)3);
addInt(payloadSize);
_buffer.setPutIndex(put);
}
}
if (_needMore)
{
if (_header == null)
{
_header = _buffers.getHeader();
}
if (_buffer == null && _header != null && _header.space() >= AJP13_MORE_CONTENT.length)
{
_header.put(AJP13_MORE_CONTENT);
_needMore = false;
}
else if (_buffer != null && _buffer.space() >= AJP13_MORE_CONTENT.length)
{
// send closing packet if all contents
// are added
_buffer.put(AJP13_MORE_CONTENT);
_needMore = false;
_bufferPrepared = true;
}
}
if (!_expectMore && _needEOC)
{
if (_buffer == null && _header.space() >= AJP13_END_RESPONSE.length)
{
_header.put(AJP13_END_RESPONSE);
_needEOC = false;
}
else if (_buffer != null && _buffer.space() >= AJP13_END_RESPONSE.length)
{
// send closing packet if all contents
// are added
_buffer.put(AJP13_END_RESPONSE);
_needEOC = false;
_bufferPrepared = true;
}
}
}
}
/* ------------------------------------------------------------ */
@Override
public boolean isComplete()
{
return !_expectMore && _state == STATE_END;
}
/* ------------------------------------------------------------ */
private void initContent() throws IOException
{
if (_buffer == null)
{
_buffer = _buffers.getBuffer();
_buffer.setPutIndex(7);
_buffer.setGetIndex(7);
}
}
/* ------------------------------------------------------------ */
private void addInt(int i)
{
_buffer.put((byte)((i >> 8) & 0xFF));
_buffer.put((byte)(i & 0xFF));
}
/* ------------------------------------------------------------ */
private void addInt(int startIndex, int i)
{
_buffer.poke(startIndex,(byte)((i >> 8) & 0xFF));
_buffer.poke((startIndex + 1),(byte)(i & 0xFF));
}
/* ------------------------------------------------------------ */
private void addString(String str) throws UnsupportedEncodingException
{
if (str == null)
{
addInt(0xFFFF);
return;
}
// TODO - need to use a writer to convert, to avoid this hacky
// conversion and temp buffer
byte[] b = str.getBytes(StringUtil.__ISO_8859_1);
addInt(b.length);
_buffer.put(b);
_buffer.put((byte)0);
}
/* ------------------------------------------------------------ */
private void addBuffer(Buffer b)
{
if (b == null)
{
addInt(0xFFFF);
return;
}
addInt(b.length());
_buffer.put(b);
_buffer.put((byte)0);
}
/* ------------------------------------------------------------ */
public void getBodyChunk() throws IOException
{
ByteArrayBuffer bf = new ByteArrayBuffer(AJP13_MORE_CONTENT);
_endp.flush(bf);
}
/* ------------------------------------------------------------ */
public void gotBody()
{
_needMore = false;
_expectMore = false;
}
/* ------------------------------------------------------------ */
public void sendCPong() throws IOException
{
Buffer buff = _buffers.getBuffer();
buff.put(AJP13_CPONG_RESPONSE);
// flushing cpong response
do
{
_endp.flush(buff);
}
while (buff.length() > 0);
_buffers.returnBuffer(buff);
reset();
}
}