001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jmeter.protocol.http.sampler;
018:
019: import java.io.ByteArrayInputStream;
020: import java.io.ByteArrayOutputStream;
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.io.OutputStream;
026: import java.net.Socket;
027: import java.net.URL;
028:
029: import org.apache.jmeter.protocol.http.control.AuthManager;
030: import org.apache.jmeter.protocol.http.control.CookieManager;
031: import org.apache.jmeter.protocol.http.control.Header;
032: import org.apache.jmeter.protocol.http.control.HeaderManager;
033: import org.apache.jmeter.testelement.property.CollectionProperty;
034: import org.apache.jmeter.testelement.property.JMeterProperty;
035: import org.apache.jmeter.testelement.property.PropertyIterator;
036: import org.apache.jmeter.util.JMeterUtils;
037: import org.apache.jorphan.logging.LoggingManager;
038: import org.apache.log.Logger;
039:
040: /**
041: * Selector for the AJP/1.3 protocol
042: * (i.e. what Tomcat uses with mod_jk)
043: * It allows you to test Tomcat in AJP mode without
044: * actually having Apache installed and configured
045: *
046: */
047: public class AjpSampler extends HTTPSamplerBase {
048:
049: private static final Logger log = LoggingManager
050: .getLoggerForClass();
051:
052: private static final char NEWLINE = '\n';
053: private static final String COLON_SPACE = ": ";//$NON-NLS-1$
054:
055: /**
056: * Translates integer codes to request header names
057: */
058: private static final String[] headerTransArray = { "accept", //$NON-NLS-1$
059: "accept-charset", //$NON-NLS-1$
060: "accept-encoding", //$NON-NLS-1$
061: "accept-language", //$NON-NLS-1$
062: "authorization", //$NON-NLS-1$
063: "connection", //$NON-NLS-1$
064: "content-type", //$NON-NLS-1$
065: "content-length", //$NON-NLS-1$
066: "cookie", //$NON-NLS-1$
067: "cookie2", //$NON-NLS-1$
068: "host", //$NON-NLS-1$
069: "pragma", //$NON-NLS-1$
070: "referer", //$NON-NLS-1$
071: "user-agent" //$NON-NLS-1$
072: };
073:
074: /**
075: * Base value for translated headers
076: */
077: static final int AJP_HEADER_BASE = 0xA000;
078:
079: static final int MAX_SEND_SIZE = 8 * 1024 - 4 - 4;
080:
081: private transient Socket channel = null;
082: private int lastPort = -1;
083: private String lastHost = null;
084: private String localName = null;
085: private String localAddress = null;
086: private byte[] inbuf = new byte[8 * 1024];
087: private byte[] outbuf = new byte[8 * 1024];
088: private transient ByteArrayOutputStream responseData = new ByteArrayOutputStream();
089: private int inpos = 0;
090: private int outpos = 0;
091: private transient InputStream body = null;
092:
093: public AjpSampler() {
094: }
095:
096: protected HTTPSampleResult sample(URL url, String method,
097: boolean frd, int fd) {
098: HTTPSampleResult res = new HTTPSampleResult();
099: res.setMonitor(false);
100: res.setSampleLabel(url.toExternalForm());
101: res.sampleStart();
102: try {
103: setupConnection(url, method, res);
104: execute(method, res);
105: res.sampleEnd();
106: res.setResponseData(responseData.toByteArray());
107: return res;
108: } catch (IOException iex) {
109: res.sampleEnd();
110: HTTPSampleResult err = errorResult(iex, res);
111: lastPort = -1; // force reopen on next sample
112: channel = null;
113: return err;
114: }
115: }
116:
117: public void threadFinished() {
118: if (channel != null) {
119: try {
120: channel.close();
121: } catch (IOException iex) {
122: log.debug("Error closing channel", iex);
123: }
124: }
125: channel = null;
126: body = null;
127: }
128:
129: private void setupConnection(URL url, String method,
130: HTTPSampleResult res) throws IOException {
131:
132: String host = url.getHost();
133: int port = url.getPort();
134: if (port <= 0 || port == url.getDefaultPort()) {
135: port = 8009;
136: }
137: String scheme = url.getProtocol();
138: if (channel == null || !host.equals(lastHost)
139: || port != lastPort) {
140: if (channel != null) {
141: channel.close();
142: }
143: channel = new Socket(host, port);
144: int timeout = JMeterUtils.getPropDefault(
145: "httpclient.timeout", 0);//$NON-NLS-1$
146: if (timeout > 0) {
147: channel.setSoTimeout(timeout);
148: }
149: localAddress = channel.getLocalAddress().getHostAddress();
150: localName = channel.getLocalAddress().getHostName();
151: lastHost = host;
152: lastPort = port;
153: }
154: res.setURL(url);
155: res.setHTTPMethod(method);
156: outpos = 4;
157: setByte((byte) 2);
158: if (method.equals(POST))
159: setByte((byte) 4);
160: else
161: setByte((byte) 2);
162: if (JMeterUtils
163: .getPropDefault("httpclient.version", "1.1").equals("1.0")) {//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
164: setString("HTTP/1.0");//$NON-NLS-1$
165: } else {
166: setString(HTTP_1_1);
167: }
168: setString(url.getFile());
169: setString(localAddress);
170: setString(localName);
171: setString(host);
172: setInt(url.getDefaultPort());
173: setByte(PROTOCOL_HTTPS.equalsIgnoreCase(scheme) ? (byte) 1
174: : (byte) 0);
175: setInt(getHeaderSize(method, url));
176: String hdr = setConnectionHeaders(url, host, method);
177: res.setRequestHeaders(hdr);
178: setConnectionCookies(url, getCookieManager());
179: setByte((byte) 0xff); // Attributes not supported
180: }
181:
182: private int getHeaderSize(String method, URL url) {
183: HeaderManager headers = getHeaderManager();
184: CookieManager cookies = getCookieManager();
185: AuthManager auth = getAuthManager();
186: int hsz = 1; // Host always
187: if (method.equals(POST)) {
188: String fn = getFilename();
189: if (fn != null && fn.trim().length() > 0) {
190: hsz += 3;
191: } else {
192: hsz += 2;
193: }
194: }
195: if (headers != null) {
196: hsz += headers.size();
197: }
198: if (cookies != null) {
199: hsz += cookies.getCookieCount();
200: }
201: if (auth != null) {
202: String authHeader = auth.getAuthHeaderForURL(url);
203: if (authHeader != null) {
204: ++hsz;
205: }
206: }
207: return hsz;
208: }
209:
210: private String setConnectionHeaders(URL url, String host,
211: String method) throws IOException {
212: HeaderManager headers = getHeaderManager();
213: AuthManager auth = getAuthManager();
214: StringBuffer hbuf = new StringBuffer();
215: // Allow Headers to override Host setting
216: hbuf
217: .append("Host").append(COLON_SPACE).append(host).append(NEWLINE);//$NON-NLS-1$
218: setInt(0xA00b); //Host
219: setString(host);
220: if (headers != null) {
221: CollectionProperty coll = headers.getHeaders();
222: PropertyIterator i = coll.iterator();
223: while (i.hasNext()) {
224: Header header = (Header) i.next().getObjectValue();
225: String n = header.getName();
226: String v = header.getValue();
227: hbuf.append(n).append(COLON_SPACE).append(v).append(
228: NEWLINE);
229: int hc = translateHeader(n);
230: if (hc > 0) {
231: setInt(hc + AJP_HEADER_BASE);
232: } else {
233: setString(n);
234: }
235: setString(v);
236: }
237: }
238: if (method.equals(POST)) {
239: int cl = -1;
240: String fn = getFilename();
241: if (fn != null && fn.trim().length() > 0) {
242: File input = new File(fn);
243: cl = (int) input.length();
244: body = new FileInputStream(input);
245: setString(HEADER_CONTENT_DISPOSITION);
246: setString("form-data; name=\"" + encode(getFileField())
247: + "\"; filename=\"" + encode(fn) + "\""); //$NON-NLS-1$ //$NON-NLS-2$
248: String mt = getMimetype();
249: hbuf.append(HEADER_CONTENT_TYPE).append(COLON_SPACE)
250: .append(mt).append(NEWLINE);
251: setInt(0xA007); // content-type
252: setString(mt);
253: } else {
254: hbuf.append(HEADER_CONTENT_TYPE).append(COLON_SPACE)
255: .append(APPLICATION_X_WWW_FORM_URLENCODED)
256: .append(NEWLINE);
257: setInt(0xA007); // content-type
258: setString(APPLICATION_X_WWW_FORM_URLENCODED);
259: StringBuffer sb = new StringBuffer();
260: boolean first = true;
261: PropertyIterator args = getArguments().iterator();
262: while (args.hasNext()) {
263: JMeterProperty arg = args.next();
264: if (first) {
265: first = false;
266: } else {
267: sb.append('&');
268: }
269: sb.append(arg.getName()).append('=').append(
270: arg.getStringValue());
271: }
272: byte[] sbody = sb.toString().getBytes(); //FIXME - encoding
273: cl = sbody.length;
274: body = new ByteArrayInputStream(sbody);
275: }
276: hbuf.append(HEADER_CONTENT_LENGTH).append(COLON_SPACE)
277: .append(String.valueOf(cl)).append(NEWLINE);
278: setInt(0xA008); // Content-length
279: setString(String.valueOf(cl));
280: }
281: if (auth != null) {
282: String authHeader = auth.getAuthHeaderForURL(url);
283: if (authHeader != null) {
284: setInt(0xA005); // Authorization
285: setString(authHeader);
286: hbuf.append(HEADER_AUTHORIZATION).append(COLON_SPACE)
287: .append(authHeader).append(NEWLINE);
288: }
289: }
290: return hbuf.toString();
291: }
292:
293: private String encode(String value) {
294: StringBuffer newValue = new StringBuffer();
295: char[] chars = value.toCharArray();
296: for (int i = 0; i < chars.length; i++) {
297: if (chars[i] == '\\')//$NON-NLS-1$
298: {
299: newValue.append("\\\\");//$NON-NLS-1$
300: } else {
301: newValue.append(chars[i]);
302: }
303: }
304: return newValue.toString();
305: }
306:
307: private void setConnectionCookies(URL url, CookieManager cookies) {
308: if (cookies != null) {
309: CollectionProperty coll = cookies.getCookies();
310: PropertyIterator i = coll.iterator();
311: while (i.hasNext()) {
312: JMeterProperty header = i.next();
313: setInt(0xA009); // Cookie
314: setString(header.getName()
315: + "=" + header.getStringValue());//$NON-NLS-1$
316: }
317: }
318: }
319:
320: private int translateHeader(String n) {
321: for (int i = 0; i < headerTransArray.length; i++) {
322: if (headerTransArray[i].equalsIgnoreCase(n)) {
323: return i + 1;
324: }
325: }
326: return -1;
327: }
328:
329: private void setByte(byte b) {
330: outbuf[outpos++] = b;
331: }
332:
333: private void setInt(int n) {
334: outbuf[outpos++] = (byte) ((n >> 8) & 0xff);
335: outbuf[outpos++] = (byte) (n & 0xff);
336: }
337:
338: private void setString(String s) {
339: if (s == null) {
340: setInt(0xFFFF);
341: } else {
342: int len = s.length();
343: setInt(len);
344: for (int i = 0; i < len; i++) {
345: setByte((byte) s.charAt(i));
346: }
347: setByte((byte) 0);
348: }
349: }
350:
351: private void send() throws IOException {
352: OutputStream os = channel.getOutputStream();
353: int len = outpos;
354: outpos = 0;
355: setInt(0x1234);
356: setInt(len - 4);
357: os.write(outbuf, 0, len);
358: }
359:
360: private void execute(String method, HTTPSampleResult res)
361: throws IOException {
362: send();
363: if (method.equals(POST)) {
364: sendPostBody();
365: }
366: handshake(res);
367: }
368:
369: private void handshake(HTTPSampleResult res) throws IOException {
370: responseData.reset();
371: int msg = getMessage();
372: while (msg != 5) {
373: if (msg == 3) {
374: int len = getInt();
375: responseData.write(inbuf, inpos, len);
376: } else if (msg == 4) {
377: parseHeaders(res);
378: } else if (msg == 6) {
379: setNextBodyChunk();
380: send();
381: }
382: msg = getMessage();
383: }
384: }
385:
386: private void sendPostBody() throws IOException {
387: setNextBodyChunk();
388: send();
389: }
390:
391: private void setNextBodyChunk() throws IOException {
392: int len = body.available();
393: if (len < 0) {
394: len = 0;
395: } else if (len > MAX_SEND_SIZE) {
396: len = MAX_SEND_SIZE;
397: }
398: outpos = 4;
399: int nr = 0;
400: if (len > 0) {
401: nr = body.read(outbuf, outpos + 2, len);
402: }
403: setInt(nr);
404: outpos += nr;
405: }
406:
407: private void parseHeaders(HTTPSampleResult res) throws IOException {
408: int status = getInt();
409: res.setResponseCode(Integer.toString(status));
410: res.setSuccessful(200 <= status && status <= 399);
411: String msg = getString();
412: res.setResponseMessage(msg);
413: int nh = getInt();
414: StringBuffer sb = new StringBuffer();
415: sb.append(HTTP_1_1).append(status)
416: .append(" ").append(msg).append(NEWLINE);//$NON-NLS-1$//$NON-NLS-2$
417: for (int i = 0; i < nh; i++) {
418: // Currently, no Tomcat version sends translated headers
419: String name;
420: int thn = peekInt();
421: if ((thn & 0xff00) == AJP_HEADER_BASE) {
422: name = headerTransArray[(thn & 0xff) - 1];
423: } else {
424: name = getString();
425: }
426: String value = getString();
427: if (HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
428: res.setContentType(value);
429: res.setEncodingAndType(value);
430: } else if (HEADER_SET_COOKIE.equalsIgnoreCase(name)) {
431: CookieManager cookies = getCookieManager();
432: if (cookies != null) {
433: cookies.addCookieFromHeader(value, res.getURL());
434: }
435: }
436: sb.append(name).append(COLON_SPACE).append(value).append(
437: NEWLINE);
438: }
439: res.setResponseHeaders(sb.toString());
440: }
441:
442: private int getMessage() throws IOException {
443: InputStream is = channel.getInputStream();
444: inpos = 0;
445: int nr = is.read(inbuf, inpos, 4);
446: if (nr != 4) {
447: channel.close();
448: channel = null;
449: throw new IOException("Connection Closed: " + nr);
450: }
451: //int mark =
452: getInt();
453: int len = getInt();
454: int toRead = len;
455: int cpos = inpos;
456: while (toRead > 0) {
457: nr = is.read(inbuf, cpos, toRead);
458: cpos += nr;
459: toRead -= nr;
460: }
461: return getByte();
462: }
463:
464: private byte getByte() {
465: return inbuf[inpos++];
466: }
467:
468: private int getInt() {
469: int res = (inbuf[inpos++] << 8) & 0xff00;
470: res += inbuf[inpos++] & 0xff;
471: return res;
472: }
473:
474: private int peekInt() {
475: int res = (inbuf[inpos] << 8) & 0xff00;
476: res += inbuf[inpos + 1] & 0xff;
477: return res;
478: }
479:
480: private String getString() throws IOException {
481: int len = getInt();
482: String s = new String(inbuf, inpos, len, "iso-8859-1");//$NON-NLS-1$
483: inpos += len + 1;
484: return s;
485: }
486: }
|