2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
18
package org.apache.coyote.ajp;
20
import java.io.ByteArrayInputStream;
21
import java.io.IOException;
22
import java.io.InputStream;
23
import java.io.InterruptedIOException;
24
import java.io.OutputStream;
25
import java.net.InetAddress;
26
import java.net.Socket;
27
import java.security.cert.CertificateFactory;
28
import java.security.cert.X509Certificate;
30
import org.apache.coyote.ActionCode;
31
import org.apache.coyote.ActionHook;
32
import org.apache.coyote.Adapter;
33
import org.apache.coyote.InputBuffer;
34
import org.apache.coyote.OutputBuffer;
35
import org.apache.coyote.Request;
36
import org.apache.coyote.RequestInfo;
37
import org.apache.coyote.Response;
38
import org.apache.tomcat.util.buf.ByteChunk;
39
import org.apache.tomcat.util.buf.HexUtils;
40
import org.apache.tomcat.util.buf.MessageBytes;
41
import org.apache.tomcat.util.http.HttpMessages;
42
import org.apache.tomcat.util.http.MimeHeaders;
43
import org.apache.tomcat.util.net.JIoEndpoint;
44
import org.apache.tomcat.util.res.StringManager;
48
* Processes HTTP requests.
50
* @author Remy Maucherat
52
* @author Dan Milstein
53
* @author Keith Wannamaker
54
* @author Kevin Seguin
55
* @author Costin Manolache
58
public class AjpProcessor implements ActionHook {
64
protected static org.apache.juli.logging.Log log
65
= org.apache.juli.logging.LogFactory.getLog(AjpProcessor.class);
68
* The string manager for this package.
70
protected static StringManager sm =
71
StringManager.getManager(Constants.Package);
74
// ----------------------------------------------------------- Constructors
77
public AjpProcessor(int packetSize, JIoEndpoint endpoint) {
79
this.endpoint = endpoint;
81
request = new Request();
82
request.setInputBuffer(new SocketInputBuffer());
84
response = new Response();
85
response.setHook(this);
86
response.setOutputBuffer(new SocketOutputBuffer());
87
request.setResponse(response);
89
this.packetSize = packetSize;
90
requestHeaderMessage = new AjpMessage(packetSize);
91
responseHeaderMessage = new AjpMessage(packetSize);
92
bodyMessage = new AjpMessage(packetSize);
94
// Set the get body message buffer
95
AjpMessage getBodyMessage = new AjpMessage(16);
96
getBodyMessage.reset();
97
getBodyMessage.appendByte(Constants.JK_AJP13_GET_BODY_CHUNK);
98
// Adjust allowed size if packetSize != default (Constants.MAX_PACKET_SIZE)
99
getBodyMessage.appendInt(Constants.MAX_READ_SIZE + packetSize - Constants.MAX_PACKET_SIZE);
100
getBodyMessage.end();
101
getBodyMessageArray = new byte[getBodyMessage.getLen()];
102
System.arraycopy(getBodyMessage.getBuffer(), 0, getBodyMessageArray,
103
0, getBodyMessage.getLen());
105
// Cause loading of HexUtils
106
int foo = HexUtils.DEC[0];
108
// Cause loading of HttpMessages
109
HttpMessages.getMessage(200);
114
// ----------------------------------------------------- Instance Variables
118
* Associated adapter.
120
protected Adapter adapter = null;
126
protected Request request = null;
132
protected Response response = null;
136
* The socket timeout used when reading the first block of the request
139
protected int packetSize;
142
* Header message. Note that this header is merely the one used during the
143
* processing of the first message of a "request", so it might not be a request
144
* header. It will stay unchanged during the processing of the whole request.
146
protected AjpMessage requestHeaderMessage = null;
150
* Message used for response header composition.
152
protected AjpMessage responseHeaderMessage = null;
158
protected AjpMessage bodyMessage = null;
164
protected MessageBytes bodyBytes = MessageBytes.newInstance();
170
protected boolean started = false;
176
protected boolean error = false;
180
* Socket associated with the current connection.
182
protected Socket socket;
188
protected InputStream input;
194
protected OutputStream output;
198
* Host name (used to avoid useless B2C conversion on the host name).
200
protected char[] hostNameC = new char[0];
204
* Associated endpoint.
206
protected JIoEndpoint endpoint;
210
* The socket timeout used when reading the first block of the request
213
protected long readTimeout;
217
* Temp message bytes used for processing.
219
protected MessageBytes tmpMB = MessageBytes.newInstance();
223
* Byte chunk for certs.
225
protected MessageBytes certificates = MessageBytes.newInstance();
229
* End of stream flag.
231
protected boolean endOfStream = false;
237
protected boolean empty = true;
243
protected boolean first = true;
249
protected boolean replay = false;
255
protected boolean finished = false;
259
* Direct buffer used for sending right away a get body message.
261
protected final byte[] getBodyMessageArray;
265
* Direct buffer used for sending right away a pong message.
267
protected static final byte[] pongMessageArray;
273
protected static final byte[] endMessageArray;
276
* Flush message array.
278
protected static final byte[] flushMessageArray;
281
// ----------------------------------------------------- Static Initializer
286
// Set the read body message buffer
287
AjpMessage pongMessage = new AjpMessage(16);
289
pongMessage.appendByte(Constants.JK_AJP13_CPONG_REPLY);
291
pongMessageArray = new byte[pongMessage.getLen()];
292
System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray,
293
0, pongMessage.getLen());
295
// Allocate the end message array
296
AjpMessage endMessage = new AjpMessage(16);
298
endMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
299
endMessage.appendByte(1);
301
endMessageArray = new byte[endMessage.getLen()];
302
System.arraycopy(endMessage.getBuffer(), 0, endMessageArray, 0,
303
endMessage.getLen());
305
// Allocate the flush message array
306
AjpMessage flushMessage = new AjpMessage(16);
307
flushMessage.reset();
308
flushMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
309
flushMessage.appendInt(0);
310
flushMessage.appendByte(0);
312
flushMessageArray = new byte[flushMessage.getLen()];
313
System.arraycopy(flushMessage.getBuffer(), 0, flushMessageArray, 0,
314
flushMessage.getLen());
319
// ------------------------------------------------------------- Properties
323
* Use Tomcat authentication ?
325
protected boolean tomcatAuthentication = true;
326
public boolean getTomcatAuthentication() { return tomcatAuthentication; }
327
public void setTomcatAuthentication(boolean tomcatAuthentication) { this.tomcatAuthentication = tomcatAuthentication; }
333
protected String requiredSecret = null;
334
public void setRequiredSecret(String requiredSecret) { this.requiredSecret = requiredSecret; }
338
* The number of milliseconds Tomcat will wait for a subsequent request
339
* before closing the connection. The default is the same as for
340
* Apache HTTP Server (15 000 milliseconds).
342
protected int keepAliveTimeout = -1;
343
public int getKeepAliveTimeout() { return keepAliveTimeout; }
344
public void setKeepAliveTimeout(int timeout) { keepAliveTimeout = timeout; }
347
// --------------------------------------------------------- Public Methods
350
/** Get the request associated with this processor.
352
* @return The request
354
public Request getRequest() {
360
* Process pipelined HTTP requests using the specified input and output
363
* @throws IOException error during an I/O operation
365
public void process(Socket socket)
367
RequestInfo rp = request.getRequestProcessor();
368
rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
370
// Setting up the socket
371
this.socket = socket;
372
input = socket.getInputStream();
373
output = socket.getOutputStream();
375
if (keepAliveTimeout > 0) {
376
soTimeout = socket.getSoTimeout();
382
while (started && !error) {
384
// Parsing the request header
386
// Set keep alive timeout if enabled
387
if (keepAliveTimeout > 0) {
388
socket.setSoTimeout(keepAliveTimeout);
390
// Get first message of the request
391
if (!readMessage(requestHeaderMessage)) {
392
// This means a connection timeout
393
rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
396
// Set back timeout if keep alive timeout is enabled
397
if (keepAliveTimeout > 0) {
398
socket.setSoTimeout(soTimeout);
400
// Check message type, process right away and break if
401
// not regular request processing
402
int type = requestHeaderMessage.getByte();
403
if (type == Constants.JK_AJP13_CPING_REQUEST) {
405
output.write(pongMessageArray);
406
} catch (IOException e) {
410
} else if(type != Constants.JK_AJP13_FORWARD_REQUEST) {
411
// Usually the servlet didn't read the previous request body
412
if(log.isDebugEnabled()) {
413
log.debug("Unexpected message: "+type);
418
request.setStartTime(System.currentTimeMillis());
419
} catch (IOException e) {
422
} catch (Throwable t) {
423
log.debug(sm.getString("ajpprocessor.header.error"), t);
425
response.setStatus(400);
429
// Setting up filters, and parse some request headers
430
rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
433
} catch (Throwable t) {
434
log.debug(sm.getString("ajpprocessor.request.prepare"), t);
435
// 400 - Internal Server Error
436
response.setStatus(400);
440
// Process the request in the adapter
443
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
444
adapter.service(request, response);
445
} catch (InterruptedIOException e) {
447
} catch (Throwable t) {
448
log.error(sm.getString("ajpprocessor.request.process"), t);
449
// 500 - Internal Server Error
450
response.setStatus(500);
455
// Finish the response if not done yet
459
} catch (Throwable t) {
464
// If there was an error, make sure the request is counted as
465
// and error, and update the statistics counter
467
response.setStatus(500);
469
request.updateCounters();
471
rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
476
rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
484
// ----------------------------------------------------- ActionHook Methods
488
* Send an action to the connector.
490
* @param actionCode Type of the action
491
* @param param Action parameter
493
public void action(ActionCode actionCode, Object param) {
495
if (actionCode == ActionCode.ACTION_COMMIT) {
497
if (response.isCommitted())
500
// Validate and write response headers
503
} catch (IOException e) {
508
} else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
510
if (!response.isCommitted()) {
511
// Validate and write response headers
514
} catch (IOException e) {
523
} catch (IOException e) {
528
} else if (actionCode == ActionCode.ACTION_CLOSE) {
531
// End the processing of the current request, and stop any further
532
// transactions with the client
536
} catch (IOException e) {
541
} else if (actionCode == ActionCode.ACTION_START) {
545
} else if (actionCode == ActionCode.ACTION_STOP) {
549
} else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
551
if (!certificates.isNull()) {
552
ByteChunk certData = certificates.getByteChunk();
553
X509Certificate jsseCerts[] = null;
554
ByteArrayInputStream bais =
555
new ByteArrayInputStream(certData.getBytes(),
557
certData.getLength());
558
// Fill the elements.
560
CertificateFactory cf =
561
CertificateFactory.getInstance("X.509");
562
while(bais.available() > 0) {
563
X509Certificate cert = (X509Certificate)
564
cf.generateCertificate(bais);
565
if(jsseCerts == null) {
566
jsseCerts = new X509Certificate[1];
569
X509Certificate [] temp = new X509Certificate[jsseCerts.length+1];
570
System.arraycopy(jsseCerts,0,temp,0,jsseCerts.length);
571
temp[jsseCerts.length] = cert;
575
} catch (java.security.cert.CertificateException e) {
576
log.error(sm.getString("ajpprocessor.certs.fail"), e);
579
request.setAttribute(JIoEndpoint.CERTIFICATE_KEY, jsseCerts);
582
} else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
584
// Get remote host name using a DNS resolution
585
if (request.remoteHost().isNull()) {
587
request.remoteHost().setString(InetAddress.getByName
588
(request.remoteAddr().toString()).getHostName());
589
} catch (IOException iex) {
594
} else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {
596
// Copy from local name for now, which should simply be an address
597
request.localAddr().setString(request.localName().toString());
599
} else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {
601
// Set the given bytes as the content
602
ByteChunk bc = (ByteChunk) param;
603
int length = bc.getLength();
604
bodyBytes.setBytes(bc.getBytes(), bc.getStart(), length);
605
request.setContentLength(length);
616
// ------------------------------------------------------ Connector Methods
620
* Set the associated adapter.
622
* @param adapter the new adapter
624
public void setAdapter(Adapter adapter) {
625
this.adapter = adapter;
630
* Get the associated adapter.
632
* @return the associated adapter
634
public Adapter getAdapter() {
639
// ------------------------------------------------------ Protected Methods
643
* After reading the request headers, we have to setup the request filters.
645
protected void prepareRequest() {
647
// Translate the HTTP method code to a String.
648
byte methodCode = requestHeaderMessage.getByte();
649
if (methodCode != Constants.SC_M_JK_STORED) {
650
String methodName = Constants.methodTransArray[(int)methodCode - 1];
651
request.method().setString(methodName);
654
requestHeaderMessage.getBytes(request.protocol());
655
requestHeaderMessage.getBytes(request.requestURI());
657
requestHeaderMessage.getBytes(request.remoteAddr());
658
requestHeaderMessage.getBytes(request.remoteHost());
659
requestHeaderMessage.getBytes(request.localName());
660
request.setLocalPort(requestHeaderMessage.getInt());
662
boolean isSSL = requestHeaderMessage.getByte() != 0;
664
request.scheme().setString("https");
668
MimeHeaders headers = request.getMimeHeaders();
670
int hCount = requestHeaderMessage.getInt();
671
for(int i = 0 ; i < hCount ; i++) {
674
// Header names are encoded as either an integer code starting
675
// with 0xA0, or as a normal string (in which case the first
676
// two bytes are the length).
677
int isc = requestHeaderMessage.peekInt();
678
int hId = isc & 0xFF;
680
MessageBytes vMB = null;
683
requestHeaderMessage.getInt(); // To advance the read position
684
hName = Constants.headerTransArray[hId - 1];
685
vMB = headers.addValue(hName);
687
// reset hId -- if the header currently being read
688
// happens to be 7 or 8 bytes long, the code below
689
// will think it's the content-type header or the
690
// content-length header - SC_REQ_CONTENT_TYPE=7,
691
// SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
692
// behaviour. see bug 5861 for more information.
694
requestHeaderMessage.getBytes(tmpMB);
695
ByteChunk bc = tmpMB.getByteChunk();
696
vMB = headers.addValue(bc.getBuffer(),
697
bc.getStart(), bc.getLength());
700
requestHeaderMessage.getBytes(vMB);
702
if (hId == Constants.SC_REQ_CONTENT_LENGTH ||
703
(hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
704
// just read the content-length header, so set it
705
long cl = vMB.getLong();
706
if(cl < Integer.MAX_VALUE)
707
request.setContentLength( (int)cl );
708
} else if (hId == Constants.SC_REQ_CONTENT_TYPE ||
709
(hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
710
// just read the content-type header, so set it
711
ByteChunk bchunk = vMB.getByteChunk();
712
request.contentType().setBytes(bchunk.getBytes(),
718
// Decode extra attributes
719
boolean secret = false;
721
while ((attributeCode = requestHeaderMessage.getByte())
722
!= Constants.SC_A_ARE_DONE) {
724
switch (attributeCode) {
726
case Constants.SC_A_REQ_ATTRIBUTE :
727
requestHeaderMessage.getBytes(tmpMB);
728
String n = tmpMB.toString();
729
requestHeaderMessage.getBytes(tmpMB);
730
String v = tmpMB.toString();
732
* AJP13 misses to forward the remotePort.
733
* Allow the AJP connector to add this info via
734
* a private request attribute.
735
* We will accept the forwarded data as the remote port,
736
* and remove it from the public list of request attributes.
738
if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) {
740
request.setRemotePort(Integer.parseInt(v));
741
} catch (NumberFormatException nfe) {
744
request.setAttribute(n, v );
748
case Constants.SC_A_CONTEXT :
749
requestHeaderMessage.getBytes(tmpMB);
753
case Constants.SC_A_SERVLET_PATH :
754
requestHeaderMessage.getBytes(tmpMB);
758
case Constants.SC_A_REMOTE_USER :
759
if (tomcatAuthentication) {
761
requestHeaderMessage.getBytes(tmpMB);
763
requestHeaderMessage.getBytes(request.getRemoteUser());
767
case Constants.SC_A_AUTH_TYPE :
768
if (tomcatAuthentication) {
770
requestHeaderMessage.getBytes(tmpMB);
772
requestHeaderMessage.getBytes(request.getAuthType());
776
case Constants.SC_A_QUERY_STRING :
777
requestHeaderMessage.getBytes(request.queryString());
780
case Constants.SC_A_JVM_ROUTE :
781
requestHeaderMessage.getBytes(request.instanceId());
784
case Constants.SC_A_SSL_CERT :
785
request.scheme().setString("https");
786
// SSL certificate extraction is lazy, moved to JkCoyoteHandler
787
requestHeaderMessage.getBytes(certificates);
790
case Constants.SC_A_SSL_CIPHER :
791
request.scheme().setString("https");
792
requestHeaderMessage.getBytes(tmpMB);
793
request.setAttribute(JIoEndpoint.CIPHER_SUITE_KEY,
797
case Constants.SC_A_SSL_SESSION :
798
request.scheme().setString("https");
799
requestHeaderMessage.getBytes(tmpMB);
800
request.setAttribute(JIoEndpoint.SESSION_ID_KEY,
804
case Constants.SC_A_SSL_KEY_SIZE :
805
request.setAttribute(JIoEndpoint.KEY_SIZE_KEY,
806
new Integer(requestHeaderMessage.getInt()));
809
case Constants.SC_A_STORED_METHOD:
810
requestHeaderMessage.getBytes(request.method());
813
case Constants.SC_A_SECRET:
814
requestHeaderMessage.getBytes(tmpMB);
815
if (requiredSecret != null) {
817
if (!tmpMB.equals(requiredSecret)) {
818
response.setStatus(403);
825
// Ignore unknown attribute for backward compatibility
832
// Check if secret was submitted if required
833
if ((requiredSecret != null) && !secret) {
834
response.setStatus(403);
838
// Check for a full URI (including protocol://host:port/)
839
ByteChunk uriBC = request.requestURI().getByteChunk();
840
if (uriBC.startsWithIgnoreCase("http", 0)) {
842
int pos = uriBC.indexOf("://", 0, 3, 4);
843
int uriBCStart = uriBC.getStart();
846
byte[] uriB = uriBC.getBytes();
847
slashPos = uriBC.indexOf('/', pos + 3);
848
if (slashPos == -1) {
849
slashPos = uriBC.getLength();
851
request.requestURI().setBytes
852
(uriB, uriBCStart + pos + 1, 1);
854
request.requestURI().setBytes
855
(uriB, uriBCStart + slashPos,
856
uriBC.getLength() - slashPos);
858
MessageBytes hostMB = headers.setValue("host");
859
hostMB.setBytes(uriB, uriBCStart + pos + 3,
865
MessageBytes valueMB = request.getMimeHeaders().getValue("host");
874
public void parseHost(MessageBytes valueMB) {
876
if (valueMB == null || (valueMB != null && valueMB.isNull()) ) {
878
request.setServerPort(request.getLocalPort());
880
request.serverName().duplicate(request.localName());
881
} catch (IOException e) {
882
response.setStatus(400);
888
ByteChunk valueBC = valueMB.getByteChunk();
889
byte[] valueB = valueBC.getBytes();
890
int valueL = valueBC.getLength();
891
int valueS = valueBC.getStart();
893
if (hostNameC.length < valueL) {
894
hostNameC = new char[valueL];
897
boolean ipv6 = (valueB[valueS] == '[');
898
boolean bracketClosed = false;
899
for (int i = 0; i < valueL; i++) {
900
char b = (char) valueB[i + valueS];
903
bracketClosed = true;
904
} else if (b == ':') {
905
if (!ipv6 || bracketClosed) {
913
if (request.scheme().equalsIgnoreCase("https")) {
914
// 443 - Default HTTPS port
915
request.setServerPort(443);
917
// 80 - Default HTTTP port
918
request.setServerPort(80);
920
request.serverName().setChars(hostNameC, 0, valueL);
923
request.serverName().setChars(hostNameC, 0, colonPos);
927
for (int i = valueL - 1; i > colonPos; i--) {
928
int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
929
if (charValue == -1) {
933
response.setStatus(400);
936
port = port + (charValue * mult);
939
request.setServerPort(port);
947
* When committing the response, we have to validate the set of headers, as
948
* well as setup the response filters.
950
protected void prepareResponse()
953
response.setCommitted(true);
955
responseHeaderMessage.reset();
956
responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS);
958
// HTTP header contents
959
responseHeaderMessage.appendInt(response.getStatus());
960
String message = null;
961
if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER &&
962
HttpMessages.isSafeInHttpHeader(response.getMessage())) {
963
message = response.getMessage();
965
if (message == null){
966
message = HttpMessages.getMessage(response.getStatus());
968
if (message == null) {
969
// mod_jk + httpd 2.x fails with a null status message - bug 45026
970
message = Integer.toString(response.getStatus());
972
tmpMB.setString(message);
973
responseHeaderMessage.appendBytes(tmpMB);
976
MimeHeaders headers = response.getMimeHeaders();
977
String contentType = response.getContentType();
978
if (contentType != null) {
979
headers.setValue("Content-Type").setString(contentType);
981
String contentLanguage = response.getContentLanguage();
982
if (contentLanguage != null) {
983
headers.setValue("Content-Language").setString(contentLanguage);
985
long contentLength = response.getContentLengthLong();
986
if (contentLength >= 0) {
987
headers.setValue("Content-Length").setLong(contentLength);
991
int numHeaders = headers.size();
992
responseHeaderMessage.appendInt(numHeaders);
993
for (int i = 0; i < numHeaders; i++) {
994
MessageBytes hN = headers.getName(i);
995
int hC = Constants.getResponseAjpIndex(hN.toString());
997
responseHeaderMessage.appendInt(hC);
1000
responseHeaderMessage.appendBytes(hN);
1002
MessageBytes hV=headers.getValue(i);
1003
responseHeaderMessage.appendBytes(hV);
1007
responseHeaderMessage.end();
1008
output.write(responseHeaderMessage.getBuffer(), 0, responseHeaderMessage.getLen());
1014
* Finish AJP response.
1016
protected void finish()
1017
throws IOException {
1019
if (!response.isCommitted()) {
1020
// Validate and write response headers
1023
} catch (IOException e) {
1034
// Add the end message
1035
output.write(endMessageArray);
1041
* Read at least the specified amount of bytes, and place them
1042
* in the input buffer.
1044
protected boolean read(byte[] buf, int pos, int n)
1045
throws IOException {
1050
res = input.read(buf, read + pos, n - read);
1054
throw new IOException(sm.getString("ajpprotocol.failedread"));
1063
/** Receive a chunk of data. Called to implement the
1064
* 'special' packet in ajp13 and to receive the data
1065
* after we send a GET_BODY packet
1067
public boolean receive() throws IOException {
1070
bodyMessage.reset();
1071
readMessage(bodyMessage);
1073
// No data received.
1074
if (bodyMessage.getLen() == 0) {
1076
// Don't mark 'end of stream' for the first chunk.
1079
int blen = bodyMessage.peekInt();
1084
bodyMessage.getBytes(bodyBytes);
1090
* Get more request body data from the web server and store it in the
1093
* @return true if there is more data, false if not.
1095
private boolean refillReadBuffer() throws IOException {
1096
// If the server returns an empty packet, assume that that end of
1097
// the stream has been reached (yuck -- fix protocol??).
1100
endOfStream = true; // we've read everything there is
1106
// Request more data immediately
1107
output.write(getBodyMessageArray);
1109
boolean moreData = receive();
1118
* Read an AJP message.
1120
* @return true if the message has been read, false if the short read
1121
* didn't return anything
1122
* @throws IOException any other failure, including incomplete reads
1124
protected boolean readMessage(AjpMessage message)
1125
throws IOException {
1127
byte[] buf = message.getBuffer();
1129
read(buf, 0, message.getHeaderLength());
1131
message.processHeader();
1132
read(buf, message.getHeaderLength(), message.getLen());
1140
* Recycle the processor.
1142
public void recycle() {
1144
// Recycle Request object
1146
endOfStream = false;
1152
certificates.recycle();
1158
* Callback to write data from the buffer.
1160
protected void flush()
1161
throws IOException {
1162
// Send the flush message
1163
output.write(flushMessageArray);
1167
// ------------------------------------- InputStreamInputBuffer Inner Class
1171
* This class is an input buffer which will read its data from an input
1174
protected class SocketInputBuffer
1175
implements InputBuffer {
1179
* Read bytes into the specified chunk.
1181
public int doRead(ByteChunk chunk, Request req )
1182
throws IOException {
1187
if (first && req.getContentLengthLong() > 0) {
1188
// Handle special first-body-chunk
1193
if (!refillReadBuffer()) {
1197
ByteChunk bc = bodyBytes.getByteChunk();
1198
chunk.setBytes(bc.getBuffer(), bc.getStart(), bc.getLength());
1200
return chunk.getLength();
1207
// ----------------------------------- OutputStreamOutputBuffer Inner Class
1211
* This class is an output buffer which will write data to an output
1214
protected class SocketOutputBuffer
1215
implements OutputBuffer {
1221
public int doWrite(ByteChunk chunk, Response res)
1222
throws IOException {
1224
if (!response.isCommitted()) {
1225
// Validate and write response headers
1228
} catch (IOException e) {
1234
int len = chunk.getLength();
1235
// 4 - hardcoded, byte[] marshalling overhead
1236
// Adjust allowed size if packetSize != default (Constants.MAX_PACKET_SIZE)
1237
int chunkSize = Constants.MAX_SEND_SIZE + packetSize - Constants.MAX_PACKET_SIZE;
1241
if (thisTime > chunkSize) {
1242
thisTime = chunkSize;
1245
responseHeaderMessage.reset();
1246
responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
1247
responseHeaderMessage.appendBytes(chunk.getBytes(), chunk.getOffset() + off, thisTime);
1248
responseHeaderMessage.end();
1249
output.write(responseHeaderMessage.getBuffer(), 0, responseHeaderMessage.getLen());
1254
return chunk.getLength();