Line 0
Link Here
|
|
|
1 |
/* |
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 |
8 |
* |
9 |
* http://www.apache.org/licenses/LICENSE-2.0 |
10 |
* |
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. |
16 |
*/ |
17 |
|
18 |
package org.apache.coyote.ajp.client; |
19 |
|
20 |
import java.io.ByteArrayInputStream; |
21 |
import java.io.ByteArrayOutputStream; |
22 |
import java.io.IOException; |
23 |
import java.io.InputStream; |
24 |
import java.io.InterruptedIOException; |
25 |
import java.io.OutputStream; |
26 |
import java.net.Socket; |
27 |
import java.net.SocketTimeoutException; |
28 |
import java.net.URL; |
29 |
import java.text.SimpleDateFormat; |
30 |
import java.util.Date; |
31 |
import java.util.Map; |
32 |
|
33 |
/** |
34 |
* AJP protocol handler class. |
35 |
* For the initial implementation see |
36 |
* http://svn.apache.org/repos/asf/jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AjpSampler.java |
37 |
* Currently does support GET and POST requests with form data content type application/x-www-form-urlencoded. multipart/form-data |
38 |
* is not currently supported. |
39 |
*/ |
40 |
public class AjpProcessor implements Runnable{ |
41 |
|
42 |
RequestContext ctx; |
43 |
|
44 |
ClientContext cc; |
45 |
|
46 |
private String responseHeader; |
47 |
|
48 |
private Statistics statistics; |
49 |
|
50 |
/** |
51 |
* Translates integer codes to request header names |
52 |
*/ |
53 |
private static final String []headerTransArray = { |
54 |
"accept", //$NON-NLS-1$ |
55 |
"accept-charset", //$NON-NLS-1$ |
56 |
"accept-encoding", //$NON-NLS-1$ |
57 |
"accept-language", //$NON-NLS-1$ |
58 |
"authorization", //$NON-NLS-1$ |
59 |
"connection", //$NON-NLS-1$ |
60 |
"content-type", //$NON-NLS-1$ |
61 |
"content-length", //$NON-NLS-1$ |
62 |
"cookie", //$NON-NLS-1$ |
63 |
"cookie2", //$NON-NLS-1$ |
64 |
"host", //$NON-NLS-1$ |
65 |
"pragma", //$NON-NLS-1$ |
66 |
"referer", //$NON-NLS-1$ |
67 |
"user-agent" //$NON-NLS-1$ |
68 |
}; |
69 |
|
70 |
// Translates integer codes to response header names |
71 |
public static final String []responseTransArray = { |
72 |
"Content-Type", |
73 |
"Content-Language", |
74 |
"Content-Length", |
75 |
"Date", |
76 |
"Last-Modified", |
77 |
"Location", |
78 |
"Set-Cookie", |
79 |
"Set-Cookie2", |
80 |
"Servlet-Engine", |
81 |
"Status", |
82 |
"WWW-Authenticate" |
83 |
}; |
84 |
|
85 |
/** |
86 |
* Base value for translated headers |
87 |
*/ |
88 |
|
89 |
private transient Socket channel = null; |
90 |
private int lastPort = -1; |
91 |
private String lastHost = null; |
92 |
private String localName = null; |
93 |
private String localAddress = null; |
94 |
private byte [] inbuf = new byte[8*1024]; |
95 |
private byte [] outbuf = new byte[8*1024]; |
96 |
private transient ByteArrayOutputStream responseData = new ByteArrayOutputStream(); |
97 |
private int inpos = 0; |
98 |
private int outpos = 0; |
99 |
private transient String stringBody = null; |
100 |
private transient InputStream body = null; |
101 |
|
102 |
|
103 |
public AjpProcessor(RequestContext ctx){ |
104 |
this.ctx=ctx; |
105 |
this.cc=ctx.getClientContext(); |
106 |
} |
107 |
|
108 |
public void run(){ |
109 |
process(); |
110 |
} |
111 |
|
112 |
public void process(){ |
113 |
try { |
114 |
int rounds=ctx.getRounds(); |
115 |
|
116 |
while(rounds-- > 0){ |
117 |
setupConnection(); |
118 |
execute(); |
119 |
cleanup(); |
120 |
cc.setStatistics(statistics); |
121 |
} |
122 |
} catch(IOException iex) { |
123 |
if(iex instanceof SocketTimeoutException){ |
124 |
statistics.setTimeout(true); |
125 |
} |
126 |
lastPort = -1; // force reopen on next sample |
127 |
channel = null; |
128 |
//return err; |
129 |
} catch(ProtocolException e){ |
130 |
lastPort = -1; // force reopen on next sample |
131 |
channel = null; |
132 |
} |
133 |
|
134 |
cc.decrementProcessorCount(); |
135 |
} |
136 |
|
137 |
public String getResponseHeader(){ |
138 |
return responseHeader; |
139 |
} |
140 |
|
141 |
public String getResponseData(){ |
142 |
return responseData.toString(); |
143 |
} |
144 |
|
145 |
private void setupConnection() throws IOException { |
146 |
URL url=ctx.getUrl(); |
147 |
statistics=new Statistics(); |
148 |
statistics.setUrl(url); |
149 |
|
150 |
if(ctx.getQueryParams().size() > 0 && ctx.getMethod().equals(Constants.GET) && url.getQuery() != null){ |
151 |
StringBuffer sb=new StringBuffer(url.toString()); |
152 |
sb.append("?"); |
153 |
|
154 |
Map<String,String> params=ctx.getQueryParams(); |
155 |
boolean first=true; |
156 |
|
157 |
for(String param:params.keySet()){ |
158 |
if(first){ |
159 |
first=false; |
160 |
}else{ |
161 |
sb.append("&"); |
162 |
} |
163 |
sb.append(param); |
164 |
sb.append("="); |
165 |
sb.append(params.get(param)); |
166 |
} |
167 |
|
168 |
url=new URL(sb.toString()); |
169 |
ctx.setUrl(url); |
170 |
} |
171 |
|
172 |
String host = url.getHost(); |
173 |
int port = url.getPort(); |
174 |
if(port <= 0 || port == url.getDefaultPort()) { |
175 |
port = 8009; |
176 |
} |
177 |
String scheme = url.getProtocol(); |
178 |
if(channel == null || !host.equals(lastHost) || port != lastPort) { |
179 |
if(channel != null) { |
180 |
channel.close(); |
181 |
} |
182 |
channel = new Socket(host, port); |
183 |
double timeout = cc.getTimeout(); |
184 |
if(timeout > 0) { |
185 |
channel.setSoTimeout((int)timeout*1000); |
186 |
} |
187 |
localAddress = channel.getLocalAddress().getHostAddress(); |
188 |
localName = channel.getLocalAddress().getHostName(); |
189 |
lastHost = host; |
190 |
lastPort = port; |
191 |
} |
192 |
log("Connected to "+host+" at port "+port); |
193 |
|
194 |
outpos = 4; |
195 |
setByte((byte)2); |
196 |
if(ctx.getMethod() != null && ctx.getMethod().equals(Constants.POST)) { |
197 |
setByte((byte)4); |
198 |
} else { |
199 |
setByte((byte)2); |
200 |
} |
201 |
if(cc.getHttpVersion() != null && (cc.getHttpVersion().equals("1.0") || cc.getHttpVersion().equals("1"))) {//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ |
202 |
setString("HTTP/1.0");//$NON-NLS-1$ |
203 |
} else { |
204 |
setString("HTTP/1.1"); |
205 |
} |
206 |
setString(url.getPath()); |
207 |
setString(localAddress); |
208 |
setString(localName); |
209 |
setString(host); |
210 |
setInt(url.getDefaultPort()); |
211 |
setByte(Constants.PROTOCOL_HTTPS.equalsIgnoreCase(scheme) ? (byte)1 : (byte)0); |
212 |
setInt(getHeaderSize()); |
213 |
setConnectionHeaders(host); |
214 |
String query = url.getQuery(); |
215 |
if (query != null) { |
216 |
setByte((byte)0x05); // Marker for query string attribute |
217 |
setString(query); |
218 |
} |
219 |
setByte((byte)0xff); // More general attributes not supported |
220 |
} |
221 |
|
222 |
private int getHeaderSize() { |
223 |
/* HeaderManager headers = getHeaderManager(); |
224 |
CookieManager cookies = getCookieManager(); |
225 |
AuthManager auth = getAuthManager(); |
226 |
int hsz = 1; // Host always |
227 |
if(method.equals(POST)) { |
228 |
HTTPFileArg[] hfa = getHTTPFiles(); |
229 |
if(hfa.length > 0) { |
230 |
hsz += 3; |
231 |
} else { |
232 |
hsz += 2; |
233 |
} |
234 |
} |
235 |
if(headers != null) { |
236 |
hsz += headers.size(); |
237 |
} |
238 |
if(cookies != null) { |
239 |
hsz += cookies.getCookieCount(); |
240 |
} |
241 |
if(auth != null) { |
242 |
String authHeader = auth.getAuthHeaderForURL(url); |
243 |
if(authHeader != null) { |
244 |
++hsz; |
245 |
} |
246 |
}*/ |
247 |
//return hsz; |
248 |
int size=1; // For host header which is compulsory. |
249 |
if(ctx.getMethod().endsWith(Constants.POST)){ |
250 |
size+=2; // For content-type and content-length headers. |
251 |
} |
252 |
|
253 |
if(ctx.getHeaders().get("host") != null){ |
254 |
size=size+ctx.getHeaders().size() - 1; // Prevent host header being counted twice |
255 |
}else{ |
256 |
size += ctx.getHeaders().size(); |
257 |
} |
258 |
return size; // For now return size of the header map. Additional header for host header. |
259 |
} |
260 |
|
261 |
|
262 |
private void setConnectionHeaders(String host) throws IOException { |
263 |
/* HeaderManager headers = getHeaderManager(); |
264 |
AuthManager auth = getAuthManager();*/ |
265 |
//StringBuilder hbuf = new StringBuilder(); |
266 |
// Allow Headers to override Host setting |
267 |
//hbuf.append("Host").append(COLON_SPACE).append(host).append(NEWLINE);//$NON-NLS-1$ |
268 |
setInt(0xA00b); //Host |
269 |
setString(host); |
270 |
/*if(headers != null) { |
271 |
CollectionProperty coll = headers.getHeaders(); |
272 |
PropertyIterator i = coll.iterator(); |
273 |
while(i.hasNext()) { |
274 |
Header header = (Header)i.next().getObjectValue(); |
275 |
String n = header.getName(); |
276 |
String v = header.getValue(); |
277 |
//hbuf.append(n).append(COLON_SPACE).append(v).append(NEWLINE); |
278 |
int hc = translateHeader(n); |
279 |
if(hc > 0) { |
280 |
setInt(hc+AJP_HEADER_BASE); |
281 |
} else { |
282 |
setString(n); |
283 |
} |
284 |
setString(v); |
285 |
} |
286 |
}*/ |
287 |
|
288 |
Map<String,String> headers=ctx.getHeaders(); |
289 |
headers.remove("host"); // Remove any repeating header for host. We already set host header. |
290 |
for(String name:headers.keySet()){ |
291 |
String value=headers.get(name); |
292 |
int code=translateHeader(name); |
293 |
if(code > 0){ |
294 |
setInt(code+Constants.AJP_HEADER_BASE); |
295 |
}else{ |
296 |
setString(name); |
297 |
} |
298 |
|
299 |
setString(value); |
300 |
} |
301 |
|
302 |
if(ctx.getMethod().equals(Constants.POST)) { |
303 |
int cl = -1; |
304 |
//HTTPFileArg[] hfa = getHTTPFiles(); |
305 |
if(ctx.getBodyFile() != null) { |
306 |
/* File input=ctx.getBodyFile(); |
307 |
cl = (int)input.length(); |
308 |
body = new FileInputStream(input); |
309 |
setString(HEADER_CONTENT_DISPOSITION); |
310 |
setString("form-data; name=\""+encode(fa.getParamName())+ |
311 |
"\"; filename=\"" + encode(fn) +"\""); //$NON-NLS-1$ //$NON-NLS-2$ |
312 |
String mt = fa.getMimeType(); |
313 |
hbuf.append(HEADER_CONTENT_TYPE).append(COLON_SPACE).append(mt).append(NEWLINE); |
314 |
setInt(0xA007); // content-type |
315 |
setString(mt);*/ |
316 |
} else { |
317 |
//hbuf.append(HEADER_CONTENT_TYPE).append(COLON_SPACE).append(APPLICATION_X_WWW_FORM_URLENCODED).append(NEWLINE); |
318 |
setInt(0xA007); // content-type |
319 |
setString(Constants.APPLICATION_X_WWW_FORM_URLENCODED); |
320 |
StringBuilder sb = new StringBuilder(); |
321 |
|
322 |
boolean first = true; |
323 |
Map<String,String> params=ctx.getQueryParams(); |
324 |
|
325 |
for(String param:params.keySet()){ |
326 |
if(first) { |
327 |
first = false; |
328 |
sb.append(param); |
329 |
sb.append("="); |
330 |
} else { |
331 |
sb.append('&'); |
332 |
} |
333 |
sb.append(params.get(param)); |
334 |
} |
335 |
stringBody = sb.toString(); |
336 |
byte [] sbody = stringBody.getBytes(); //FIXME - encoding |
337 |
cl = sbody.length; |
338 |
body = new ByteArrayInputStream(sbody); |
339 |
} |
340 |
//hbuf.append(HEADER_CONTENT_LENGTH).append(COLON_SPACE).append(String.valueOf(cl)).append(NEWLINE); |
341 |
setInt(0xA008); // Content-length |
342 |
setString(String.valueOf(cl)); |
343 |
} |
344 |
/* if(auth != null) { |
345 |
String authHeader = auth.getAuthHeaderForURL(url); |
346 |
if(authHeader != null) { |
347 |
setInt(0xA005); // Authorization |
348 |
setString(authHeader); |
349 |
hbuf.append(HEADER_AUTHORIZATION).append(COLON_SPACE).append(authHeader).append(NEWLINE); |
350 |
} |
351 |
} |
352 |
return hbuf.toString();*/ |
353 |
} |
354 |
|
355 |
private String encode(String value) { |
356 |
StringBuilder newValue = new StringBuilder(); |
357 |
char[] chars = value.toCharArray(); |
358 |
for (int i = 0; i < chars.length; i++) |
359 |
{ |
360 |
if (chars[i] == '\\')//$NON-NLS-1$ |
361 |
{ |
362 |
newValue.append("\\\\");//$NON-NLS-1$ |
363 |
} |
364 |
else |
365 |
{ |
366 |
newValue.append(chars[i]); |
367 |
} |
368 |
} |
369 |
return newValue.toString(); |
370 |
} |
371 |
|
372 |
/*private String setConnectionCookies(URL url, CookieManager cookies) { |
373 |
String cookieHeader = null; |
374 |
if(cookies != null) { |
375 |
cookieHeader = cookies.getCookieHeaderForURL(url); |
376 |
CollectionProperty coll = cookies.getCookies(); |
377 |
PropertyIterator i = coll.iterator(); |
378 |
while(i.hasNext()) { |
379 |
Cookie cookie = (Cookie)(i.next().getObjectValue()); |
380 |
setInt(0xA009); // Cookie |
381 |
setString(cookie.getName()+"="+cookie.getValue());//$NON-NLS-1$ |
382 |
} |
383 |
} |
384 |
return cookieHeader; |
385 |
}*/ |
386 |
|
387 |
private int translateHeader(String n) { |
388 |
for(int i=0; i < headerTransArray.length; i++) { |
389 |
if(headerTransArray[i].equalsIgnoreCase(n)) { |
390 |
return i+1; |
391 |
} |
392 |
} |
393 |
return -1; |
394 |
} |
395 |
|
396 |
private void setByte(byte b) { |
397 |
outbuf[outpos++] = b; |
398 |
} |
399 |
|
400 |
private void setInt(int n) { |
401 |
outbuf[outpos++] = (byte)((n >> 8)&0xff); |
402 |
outbuf[outpos++] = (byte) (n&0xff); |
403 |
} |
404 |
|
405 |
private void setString(String s) { |
406 |
if( s == null ) { |
407 |
setInt(0xFFFF); |
408 |
} else { |
409 |
int len = s.length(); |
410 |
setInt(len); |
411 |
for(int i=0; i < len; i++) { |
412 |
setByte((byte)s.charAt(i)); |
413 |
} |
414 |
setByte((byte)0); |
415 |
} |
416 |
} |
417 |
|
418 |
private void send() throws IOException { |
419 |
OutputStream os = channel.getOutputStream(); |
420 |
int len = outpos; |
421 |
outpos = 0; |
422 |
setInt(0x1234); |
423 |
setInt(len-4); |
424 |
os.write(outbuf, 0, len); |
425 |
} |
426 |
|
427 |
private void execute() throws IOException, ProtocolException { |
428 |
String dateTime=new SimpleDateFormat("yy/MM/dd HH:mm:ss").format(new Date()); |
429 |
statistics.setDateTime(dateTime); |
430 |
long start=System.currentTimeMillis(); |
431 |
send(); |
432 |
if(ctx.getMethod() != null && ctx.getMethod().equals(Constants.POST)) { |
433 |
sendPostBody(); |
434 |
} |
435 |
handshake(); |
436 |
long end=System.currentTimeMillis(); |
437 |
statistics.setTimeElapsed(end-start); |
438 |
} |
439 |
|
440 |
private void handshake() throws IOException, ProtocolException { |
441 |
responseData.reset(); |
442 |
int msg = getMessage(); |
443 |
while(msg != 5) { |
444 |
if(msg == 3) { |
445 |
int len = getInt(); |
446 |
responseData.write(inbuf, inpos, len); |
447 |
} else if(msg == 4) { |
448 |
responseHeader=parseHeaders(); |
449 |
} else if(msg == 6) { |
450 |
setNextBodyChunk(); |
451 |
send(); |
452 |
} |
453 |
msg = getMessage(); |
454 |
} |
455 |
} |
456 |
|
457 |
private void sendPostBody() throws IOException { |
458 |
setNextBodyChunk(); |
459 |
send(); |
460 |
} |
461 |
|
462 |
private void setNextBodyChunk() throws IOException { |
463 |
int len = body.available(); |
464 |
if(len < 0) { |
465 |
len = 0; |
466 |
} else if(len > Constants.MAX_SEND_SIZE) { |
467 |
len = Constants.MAX_SEND_SIZE; |
468 |
} |
469 |
outpos = 4; |
470 |
int nr = 0; |
471 |
if(len > 0) { |
472 |
nr = body.read(outbuf, outpos+2, len); |
473 |
} |
474 |
setInt(nr); |
475 |
outpos += nr; |
476 |
} |
477 |
|
478 |
|
479 |
private String parseHeaders() throws IOException { |
480 |
int status = getInt(); |
481 |
statistics.setReplyCode(status); |
482 |
|
483 |
String msg = getString(null); |
484 |
int nh = getInt(); |
485 |
StringBuilder sb = new StringBuilder(); |
486 |
sb.append(Constants.HTTP_1_1 ).append(status).append(" ").append(msg).append(Constants.NEWLINE);//$NON-NLS-1$//$NON-NLS-2$ |
487 |
for(int i=0; i < nh; i++) { |
488 |
// Currently, no Tomcat version sends translated headers |
489 |
String name; |
490 |
int thn = peekInt(); |
491 |
if((thn & 0xff00) == Constants.AJP_HEADER_BASE) { |
492 |
name = responseTransArray[(thn&0xff)-1]; |
493 |
getInt(); |
494 |
} else { |
495 |
name = getString(sb); |
496 |
} |
497 |
String value = getString(sb); |
498 |
/* if(HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) { |
499 |
res.setContentType(value); |
500 |
res.setEncodingAndType(value); |
501 |
} else if(HEADER_SET_COOKIE.equalsIgnoreCase(name)) { |
502 |
CookieManager cookies = getCookieManager(); |
503 |
if(cookies != null) { |
504 |
cookies.addCookieFromHeader(value, res.getURL()); |
505 |
} |
506 |
}*/ |
507 |
sb.append(name).append(Constants.COLON_SPACE).append(value).append(Constants.NEWLINE); |
508 |
} |
509 |
|
510 |
return sb.toString(); |
511 |
} |
512 |
|
513 |
|
514 |
private int getMessage() throws ProtocolException, IOException { |
515 |
InputStream is = channel.getInputStream(); |
516 |
inpos = 0; |
517 |
int nr=0; |
518 |
|
519 |
try{ |
520 |
nr = is.read(inbuf, inpos, 4); |
521 |
}catch(InterruptedIOException e){ |
522 |
throw new SocketTimeoutException(e.getMessage()); |
523 |
} |
524 |
|
525 |
if(nr != 4) { |
526 |
channel.close(); |
527 |
channel = null; |
528 |
throw new ProtocolException("Protocol Error. Unexpected response."); |
529 |
} |
530 |
//int mark = |
531 |
getInt(); |
532 |
int len = getInt(); |
533 |
int toRead = len; |
534 |
int cpos = inpos; |
535 |
while(toRead > 0) { |
536 |
nr = is.read(inbuf, cpos, toRead); |
537 |
cpos += nr; |
538 |
toRead -= nr; |
539 |
} |
540 |
return getByte(); |
541 |
} |
542 |
|
543 |
private byte getByte() { |
544 |
return inbuf[inpos++]; |
545 |
} |
546 |
|
547 |
private int getInt() { |
548 |
int res = (inbuf[inpos++]<<8)&0xff00; |
549 |
res += inbuf[inpos++]&0xff; |
550 |
return res; |
551 |
} |
552 |
|
553 |
private int peekInt() { |
554 |
int res = (inbuf[inpos]<<8)&0xff00; |
555 |
res += inbuf[inpos+1]&0xff; |
556 |
return res; |
557 |
} |
558 |
|
559 |
private String getString(StringBuilder sb) throws IOException { |
560 |
int len = getInt(); |
561 |
String s = new String(inbuf, inpos, len, "iso-8859-1");//$NON-NLS-1$ |
562 |
inpos+= len+1; |
563 |
return s; |
564 |
} |
565 |
|
566 |
private void cleanup(){ |
567 |
responseHeader=null; |
568 |
responseData.reset(); |
569 |
} |
570 |
|
571 |
private void log(String message){ |
572 |
if(cc.isVerbose()){ |
573 |
cc.getOutput().print(message); |
574 |
} |
575 |
} |
576 |
|
577 |
class ProtocolException extends Exception{ |
578 |
|
579 |
private static final long serialVersionUID = 149146185282537074L; |
580 |
|
581 |
public ProtocolException(String message){ |
582 |
super(message); |
583 |
} |
584 |
|
585 |
} |
586 |
} |
587 |
|
588 |
|