Index: AJPClient.java =================================================================== --- AJPClient.java (revision 0) +++ AJPClient.java (revision 0) @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.coyote.ajp.client; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.Set; + +public class AJPClient { + + int timeout; + + File statistics; + + String bind_address; + + File header_file; + + String headers[]; + + String user_agent; + + String urls[]; + + ClientContext cc=new ClientContext(); + + AjpProcessor[] processors; + + public static void main(String args[]) throws Exception{ + AJPClient client=new AJPClient(); + client.parseArguments(args); + client.init(); + client.run(); + client.output(); + } + + private void parseArguments(String args[]){ + int index = 0; + String arg; + ArrayList urlArray=new ArrayList(); + + while (index < args.length) { + arg = args[index++]; + String[] tokens=arg.split("="); + + String name=null; + String value=null; + boolean isSwitch=false; + if(tokens.length == 2){ + name=tokens[0]; + value=tokens[1]; + + if (name.equals("-T") || name.equals("--timeout")) { + cc.timeout = Double.parseDouble(value); + isSwitch=true; + } else if (name.equals("-o") || name.equals("--output")) { + if(value.equals("-")){ + cc.output=System.out; + }else{ + try{ + FileOutputStream fs=new FileOutputStream(value); + cc.output= new PrintStream(fs); + }catch(FileNotFoundException e){ + cc.output=System.out; + System.out.println("Failed loading out put file. Output redirected to console..."); + } + } + isSwitch=true; + } else if (name.equals("-r") || name.equals("--requests-file")) { + cc.requests_file=new File(value); + isSwitch=true; + } else if (name.equals("-H") || name.equals("--headers")) { + String[] tmp=null; + if(value.contains("\\")){ + tmp=value.split("\\\\"); + }else{ + tmp=new String[1]; + tmp[0]=value; + } + + for(String header:tmp){ + String[] tuple=header.split(":"); + cc.headers.put(tuple[0], tuple[1]); // Header name and header value + } + isSwitch=true; + } else if (name.equals("--rounds")) { + cc.rounds=Integer.parseInt(value); + isSwitch=true; + } else if (name.equals("--http-version")) { + cc.http_version=value; + isSwitch=true; + } else if (name.equals("--body-file")) { // This switch is not used currently. + cc.bodyFile=new File(value); + isSwitch=true; + } else if (name.equals("--query")) { + String[] tmp=null; + if(value.contains("\\")){ + tmp=value.split("\\\\"); + }else{ + tmp=new String[1]; + tmp[0]=value; + } + + for(String param:tmp){ + String[] tuple=param.split(":"); + cc.queries.put(tuple[0], tuple[1]); // Parameter name and parameter value + } + isSwitch=true; + } else if (name.equals("-v") || name.equals("--verbose")) { + cc.verbose=true; + } else if (name.equals("-u") || name.equals("--user-agent")) { + cc.headers.put("User-Agent", arg); + isSwitch=true; + } else if (name.equals("-m") || name.equals("--method")) { + String method=value; + if(method.trim().equalsIgnoreCase(Constants.POST)){ + cc.method=Constants.POST; + }else{ + cc.method=Constants.GET; + } + isSwitch=true; + } else if (name.equals("-h") || name.equals("--help")) { + printUsage(); + System.exit(0); + } + } + + if(!(arg.startsWith("-")|| arg.startsWith("--"))){ // We have come to the URL section. + urlArray.add(arg); + } else if(!isSwitch) { + printUsage(); + System.exit(-1); + } + } + + if(cc.method == null){ + cc.method=Constants.GET; + } + + if(cc.method.equals(Constants.GET) && cc.bodyFile != null){ // GET cannot have a body + error("-m","--bodyFile"); + System.exit(-1); + } + + cc.urls=urlArray.toArray(cc.urls); + urlArray=null; + } + + private void printUsage(){ + System.out.println("java -jar AJPClient.jar [Options] url[1-n]\n"); + System.out.println("Options\n"); + System.out.println("\t-t\n\t--timeout=seconds\n\t\tSet the read timeout to seconds seconds.\n"); + System.out.println("\t-m\n\t--method=method\n\t\tSets the HTTP method." + + " Can be either GET or POST. No other methods are supported at present.\n"); + System.out.println("\t--rounds=number\n\t\tSets the number of times url[s] given in the command are repeatedly fetched.\n"); + System.out.println("\t--http-version=version\n\t\tSets the HTTP version. Can be either 1.0 or 1.1.\n"); + System.out.println("\t--query=param_1:paramValue|...|param_n:paramValue\n\t\t" + + "Sets the parameters in the query string for the url[s] specified in the command line.\n"); + System.out.println("\t-r\n\t--requests-file=file\n\t\tSets the requests.xml configuration file.\n"); + System.out.println("\t-o\n\t--output=file\n\t\tSets the output file location. If - is specified then outputs to the console.\n"); + System.out.println("\t-H\n\t--headers=header_1:value|....|header_2:value\n\t\t" + + "Sets headers to be included in HTTP requests for url[s] specified in the command line.\n"); + } + + private void error(String... switches){ + System.out.println("ERROR: Invalid combination of switches. "); + for(String ss:switches){ + System.out.print(ss+" "); + } + System.out.println("cannot be used together."); + System.out.println(); + printUsage(); + } + + private void init() throws Exception{ + cc.init(); + Set ctxs=cc.getRequestContexts(); + processors=new AjpProcessor[ctxs.size()]; + + int index=0; + for(Iterator it=ctxs.iterator();it.hasNext();){ + AjpProcessor processor=new AjpProcessor(it.next()); + processors[index++]=processor; + } + } + + private void run(){ + for(AjpProcessor processor:processors){ + new Thread(processor).start(); + } + + synchronized(cc){ + while(cc.getProcessorCount() > 0){ + try { + cc.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + private void output() throws IOException{ + ArrayList stats=cc.getStatistics(); + PrintStream ps=cc.getOutput(); + + String format = "|%1$-15s|%2$-20s|%3$-10s|%4$-15s|%5$-10s|%6$-75s\n"; + ps.println("\nRun: "+new SimpleDateFormat("yy/MM/dd HH:mm:ss").format(new Date())); + ps.println(); + ps.format(format, "Connection","StartTime","TimedOut","TimeElapsed(ms)","ReplyCode","URL"); + ps.println(); + + int counter=0; + for(Iterator it=stats.iterator();it.hasNext();){ + Statistics statistics=it.next(); + ps.format(format, counter++,statistics.getDateTime(),statistics.isTimeout(),statistics.getTimeElapsed(), + statistics.getReplyCode(),statistics.getUrl()); + } + + ps.flush(); + ps.close(); + } + +} Index: AjpProcessor.java =================================================================== --- AjpProcessor.java (revision 0) +++ AjpProcessor.java (revision 0) @@ -0,0 +1,588 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.coyote.ajp.client; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + +/** + * AJP protocol handler class. + * For the initial implementation see + * http://svn.apache.org/repos/asf/jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/AjpSampler.java + * Currently does support GET and POST requests with form data content type application/x-www-form-urlencoded. multipart/form-data + * is not currently supported. + */ +public class AjpProcessor implements Runnable{ + + RequestContext ctx; + + ClientContext cc; + + private String responseHeader; + + private Statistics statistics; + + /** + * Translates integer codes to request header names + */ + private static final String []headerTransArray = { + "accept", //$NON-NLS-1$ + "accept-charset", //$NON-NLS-1$ + "accept-encoding", //$NON-NLS-1$ + "accept-language", //$NON-NLS-1$ + "authorization", //$NON-NLS-1$ + "connection", //$NON-NLS-1$ + "content-type", //$NON-NLS-1$ + "content-length", //$NON-NLS-1$ + "cookie", //$NON-NLS-1$ + "cookie2", //$NON-NLS-1$ + "host", //$NON-NLS-1$ + "pragma", //$NON-NLS-1$ + "referer", //$NON-NLS-1$ + "user-agent" //$NON-NLS-1$ + }; + + // Translates integer codes to response header names + public static final String []responseTransArray = { + "Content-Type", + "Content-Language", + "Content-Length", + "Date", + "Last-Modified", + "Location", + "Set-Cookie", + "Set-Cookie2", + "Servlet-Engine", + "Status", + "WWW-Authenticate" + }; + + /** + * Base value for translated headers + */ + + private transient Socket channel = null; + private int lastPort = -1; + private String lastHost = null; + private String localName = null; + private String localAddress = null; + private byte [] inbuf = new byte[8*1024]; + private byte [] outbuf = new byte[8*1024]; + private transient ByteArrayOutputStream responseData = new ByteArrayOutputStream(); + private int inpos = 0; + private int outpos = 0; + private transient String stringBody = null; + private transient InputStream body = null; + + + public AjpProcessor(RequestContext ctx){ + this.ctx=ctx; + this.cc=ctx.getClientContext(); + } + + public void run(){ + process(); + } + + public void process(){ + try { + int rounds=ctx.getRounds(); + + while(rounds-- > 0){ + setupConnection(); + execute(); + cleanup(); + cc.setStatistics(statistics); + } + } catch(IOException iex) { + if(iex instanceof SocketTimeoutException){ + statistics.setTimeout(true); + } + lastPort = -1; // force reopen on next sample + channel = null; + //return err; + } catch(ProtocolException e){ + lastPort = -1; // force reopen on next sample + channel = null; + } + + cc.decrementProcessorCount(); + } + + public String getResponseHeader(){ + return responseHeader; + } + + public String getResponseData(){ + return responseData.toString(); + } + + private void setupConnection() throws IOException { + URL url=ctx.getUrl(); + statistics=new Statistics(); + statistics.setUrl(url); + + if(ctx.getQueryParams().size() > 0 && ctx.getMethod().equals(Constants.GET) && url.getQuery() != null){ + StringBuffer sb=new StringBuffer(url.toString()); + sb.append("?"); + + Map params=ctx.getQueryParams(); + boolean first=true; + + for(String param:params.keySet()){ + if(first){ + first=false; + }else{ + sb.append("&"); + } + sb.append(param); + sb.append("="); + sb.append(params.get(param)); + } + + url=new URL(sb.toString()); + ctx.setUrl(url); + } + + String host = url.getHost(); + int port = url.getPort(); + if(port <= 0 || port == url.getDefaultPort()) { + port = 8009; + } + String scheme = url.getProtocol(); + if(channel == null || !host.equals(lastHost) || port != lastPort) { + if(channel != null) { + channel.close(); + } + channel = new Socket(host, port); + double timeout = cc.getTimeout(); + if(timeout > 0) { + channel.setSoTimeout((int)timeout*1000); + } + localAddress = channel.getLocalAddress().getHostAddress(); + localName = channel.getLocalAddress().getHostName(); + lastHost = host; + lastPort = port; + } + log("Connected to "+host+" at port "+port); + + outpos = 4; + setByte((byte)2); + if(ctx.getMethod() != null && ctx.getMethod().equals(Constants.POST)) { + setByte((byte)4); + } else { + setByte((byte)2); + } + if(cc.getHttpVersion() != null && (cc.getHttpVersion().equals("1.0") || cc.getHttpVersion().equals("1"))) {//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + setString("HTTP/1.0");//$NON-NLS-1$ + } else { + setString("HTTP/1.1"); + } + setString(url.getPath()); + setString(localAddress); + setString(localName); + setString(host); + setInt(url.getDefaultPort()); + setByte(Constants.PROTOCOL_HTTPS.equalsIgnoreCase(scheme) ? (byte)1 : (byte)0); + setInt(getHeaderSize()); + setConnectionHeaders(host); + String query = url.getQuery(); + if (query != null) { + setByte((byte)0x05); // Marker for query string attribute + setString(query); + } + setByte((byte)0xff); // More general attributes not supported + } + + private int getHeaderSize() { + /* HeaderManager headers = getHeaderManager(); + CookieManager cookies = getCookieManager(); + AuthManager auth = getAuthManager(); + int hsz = 1; // Host always + if(method.equals(POST)) { + HTTPFileArg[] hfa = getHTTPFiles(); + if(hfa.length > 0) { + hsz += 3; + } else { + hsz += 2; + } + } + if(headers != null) { + hsz += headers.size(); + } + if(cookies != null) { + hsz += cookies.getCookieCount(); + } + if(auth != null) { + String authHeader = auth.getAuthHeaderForURL(url); + if(authHeader != null) { + ++hsz; + } + }*/ + //return hsz; + int size=1; // For host header which is compulsory. + if(ctx.getMethod().endsWith(Constants.POST)){ + size+=2; // For content-type and content-length headers. + } + + if(ctx.getHeaders().get("host") != null){ + size=size+ctx.getHeaders().size() - 1; // Prevent host header being counted twice + }else{ + size += ctx.getHeaders().size(); + } + return size; // For now return size of the header map. Additional header for host header. + } + + + private void setConnectionHeaders(String host) throws IOException { + /* HeaderManager headers = getHeaderManager(); + AuthManager auth = getAuthManager();*/ + //StringBuilder hbuf = new StringBuilder(); + // Allow Headers to override Host setting + //hbuf.append("Host").append(COLON_SPACE).append(host).append(NEWLINE);//$NON-NLS-1$ + setInt(0xA00b); //Host + setString(host); + /*if(headers != null) { + CollectionProperty coll = headers.getHeaders(); + PropertyIterator i = coll.iterator(); + while(i.hasNext()) { + Header header = (Header)i.next().getObjectValue(); + String n = header.getName(); + String v = header.getValue(); + //hbuf.append(n).append(COLON_SPACE).append(v).append(NEWLINE); + int hc = translateHeader(n); + if(hc > 0) { + setInt(hc+AJP_HEADER_BASE); + } else { + setString(n); + } + setString(v); + } + }*/ + + Map headers=ctx.getHeaders(); + headers.remove("host"); // Remove any repeating header for host. We already set host header. + for(String name:headers.keySet()){ + String value=headers.get(name); + int code=translateHeader(name); + if(code > 0){ + setInt(code+Constants.AJP_HEADER_BASE); + }else{ + setString(name); + } + + setString(value); + } + + if(ctx.getMethod().equals(Constants.POST)) { + int cl = -1; + //HTTPFileArg[] hfa = getHTTPFiles(); + if(ctx.getBodyFile() != null) { +/* File input=ctx.getBodyFile(); + cl = (int)input.length(); + body = new FileInputStream(input); + setString(HEADER_CONTENT_DISPOSITION); + setString("form-data; name=\""+encode(fa.getParamName())+ + "\"; filename=\"" + encode(fn) +"\""); //$NON-NLS-1$ //$NON-NLS-2$ + String mt = fa.getMimeType(); + hbuf.append(HEADER_CONTENT_TYPE).append(COLON_SPACE).append(mt).append(NEWLINE); + setInt(0xA007); // content-type + setString(mt);*/ + } else { + //hbuf.append(HEADER_CONTENT_TYPE).append(COLON_SPACE).append(APPLICATION_X_WWW_FORM_URLENCODED).append(NEWLINE); + setInt(0xA007); // content-type + setString(Constants.APPLICATION_X_WWW_FORM_URLENCODED); + StringBuilder sb = new StringBuilder(); + + boolean first = true; + Map params=ctx.getQueryParams(); + + for(String param:params.keySet()){ + if(first) { + first = false; + sb.append(param); + sb.append("="); + } else { + sb.append('&'); + } + sb.append(params.get(param)); + } + stringBody = sb.toString(); + byte [] sbody = stringBody.getBytes(); //FIXME - encoding + cl = sbody.length; + body = new ByteArrayInputStream(sbody); + } + //hbuf.append(HEADER_CONTENT_LENGTH).append(COLON_SPACE).append(String.valueOf(cl)).append(NEWLINE); + setInt(0xA008); // Content-length + setString(String.valueOf(cl)); + } +/* if(auth != null) { + String authHeader = auth.getAuthHeaderForURL(url); + if(authHeader != null) { + setInt(0xA005); // Authorization + setString(authHeader); + hbuf.append(HEADER_AUTHORIZATION).append(COLON_SPACE).append(authHeader).append(NEWLINE); + } + } + return hbuf.toString();*/ + } + + private String encode(String value) { + StringBuilder newValue = new StringBuilder(); + char[] chars = value.toCharArray(); + for (int i = 0; i < chars.length; i++) + { + if (chars[i] == '\\')//$NON-NLS-1$ + { + newValue.append("\\\\");//$NON-NLS-1$ + } + else + { + newValue.append(chars[i]); + } + } + return newValue.toString(); + } + + /*private String setConnectionCookies(URL url, CookieManager cookies) { + String cookieHeader = null; + if(cookies != null) { + cookieHeader = cookies.getCookieHeaderForURL(url); + CollectionProperty coll = cookies.getCookies(); + PropertyIterator i = coll.iterator(); + while(i.hasNext()) { + Cookie cookie = (Cookie)(i.next().getObjectValue()); + setInt(0xA009); // Cookie + setString(cookie.getName()+"="+cookie.getValue());//$NON-NLS-1$ + } + } + return cookieHeader; + }*/ + + private int translateHeader(String n) { + for(int i=0; i < headerTransArray.length; i++) { + if(headerTransArray[i].equalsIgnoreCase(n)) { + return i+1; + } + } + return -1; + } + + private void setByte(byte b) { + outbuf[outpos++] = b; + } + + private void setInt(int n) { + outbuf[outpos++] = (byte)((n >> 8)&0xff); + outbuf[outpos++] = (byte) (n&0xff); + } + + private void setString(String s) { + if( s == null ) { + setInt(0xFFFF); + } else { + int len = s.length(); + setInt(len); + for(int i=0; i < len; i++) { + setByte((byte)s.charAt(i)); + } + setByte((byte)0); + } + } + + private void send() throws IOException { + OutputStream os = channel.getOutputStream(); + int len = outpos; + outpos = 0; + setInt(0x1234); + setInt(len-4); + os.write(outbuf, 0, len); + } + + private void execute() throws IOException, ProtocolException { + String dateTime=new SimpleDateFormat("yy/MM/dd HH:mm:ss").format(new Date()); + statistics.setDateTime(dateTime); + long start=System.currentTimeMillis(); + send(); + if(ctx.getMethod() != null && ctx.getMethod().equals(Constants.POST)) { + sendPostBody(); + } + handshake(); + long end=System.currentTimeMillis(); + statistics.setTimeElapsed(end-start); + } + + private void handshake() throws IOException, ProtocolException { + responseData.reset(); + int msg = getMessage(); + while(msg != 5) { + if(msg == 3) { + int len = getInt(); + responseData.write(inbuf, inpos, len); + } else if(msg == 4) { + responseHeader=parseHeaders(); + } else if(msg == 6) { + setNextBodyChunk(); + send(); + } + msg = getMessage(); + } + } + + private void sendPostBody() throws IOException { + setNextBodyChunk(); + send(); + } + + private void setNextBodyChunk() throws IOException { + int len = body.available(); + if(len < 0) { + len = 0; + } else if(len > Constants.MAX_SEND_SIZE) { + len = Constants.MAX_SEND_SIZE; + } + outpos = 4; + int nr = 0; + if(len > 0) { + nr = body.read(outbuf, outpos+2, len); + } + setInt(nr); + outpos += nr; + } + + + private String parseHeaders() throws IOException { + int status = getInt(); + statistics.setReplyCode(status); + + String msg = getString(null); + int nh = getInt(); + StringBuilder sb = new StringBuilder(); + sb.append(Constants.HTTP_1_1 ).append(status).append(" ").append(msg).append(Constants.NEWLINE);//$NON-NLS-1$//$NON-NLS-2$ + for(int i=0; i < nh; i++) { + // Currently, no Tomcat version sends translated headers + String name; + int thn = peekInt(); + if((thn & 0xff00) == Constants.AJP_HEADER_BASE) { + name = responseTransArray[(thn&0xff)-1]; + getInt(); + } else { + name = getString(sb); + } + String value = getString(sb); + /* if(HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) { + res.setContentType(value); + res.setEncodingAndType(value); + } else if(HEADER_SET_COOKIE.equalsIgnoreCase(name)) { + CookieManager cookies = getCookieManager(); + if(cookies != null) { + cookies.addCookieFromHeader(value, res.getURL()); + } + }*/ + sb.append(name).append(Constants.COLON_SPACE).append(value).append(Constants.NEWLINE); + } + + return sb.toString(); + } + + + private int getMessage() throws ProtocolException, IOException { + InputStream is = channel.getInputStream(); + inpos = 0; + int nr=0; + + try{ + nr = is.read(inbuf, inpos, 4); + }catch(InterruptedIOException e){ + throw new SocketTimeoutException(e.getMessage()); + } + + if(nr != 4) { + channel.close(); + channel = null; + throw new ProtocolException("Protocol Error. Unexpected response."); + } + //int mark = + getInt(); + int len = getInt(); + int toRead = len; + int cpos = inpos; + while(toRead > 0) { + nr = is.read(inbuf, cpos, toRead); + cpos += nr; + toRead -= nr; + } + return getByte(); + } + + private byte getByte() { + return inbuf[inpos++]; + } + + private int getInt() { + int res = (inbuf[inpos++]<<8)&0xff00; + res += inbuf[inpos++]&0xff; + return res; + } + + private int peekInt() { + int res = (inbuf[inpos]<<8)&0xff00; + res += inbuf[inpos+1]&0xff; + return res; + } + + private String getString(StringBuilder sb) throws IOException { + int len = getInt(); + String s = new String(inbuf, inpos, len, "iso-8859-1");//$NON-NLS-1$ + inpos+= len+1; + return s; + } + + private void cleanup(){ + responseHeader=null; + responseData.reset(); + } + + private void log(String message){ + if(cc.isVerbose()){ + cc.getOutput().print(message); + } + } + + class ProtocolException extends Exception{ + + private static final long serialVersionUID = 149146185282537074L; + + public ProtocolException(String message){ + super(message); + } + + } +} + + Index: ClientContext.java =================================================================== --- ClientContext.java (revision 0) +++ ClientContext.java (revision 0) @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.coyote.ajp.client; + +import java.io.File; +import java.io.PrintStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class ClientContext { + + double timeout; + + int rounds; + + int processorCount; + + boolean verbose; + + PrintStream output; + + String bind_address; + + File requests_file; + + String user_agent; + + String urls[]; + + Map headers; + + Map defaultHeaders; + + Map queries; + + Set ctxs; + + String http_version; + + ArrayList stats; + + String method; + + File bodyFile; + + + public ClientContext(){ + headers=new HashMap(); + defaultHeaders=new HashMap(); + queries=new HashMap(); + ctxs=new HashSet(); + stats=new ArrayList(); + urls=new String[0]; + rounds=1; + populateDefaultHeaders(); + } + + public void init() throws Exception{ + + // Set requests from configuration file. + try{ + if(requests_file!= null){ + parse(); + } + }catch(Exception e){ + throw e; + } + + // Set requests from command line. + for(int i=0;i getRequestContexts(){ + return ctxs; + } + + public double getTimeout(){ + return timeout; + } + + public String getHttpVersion(){ + return http_version; + } + + public boolean isVerbose(){ + return verbose; + } + + public void setStatistics(Statistics statistics){ + synchronized(stats){ + stats.add(statistics); + } + } + + public ArrayList getStatistics(){ + return stats; + } + + public PrintStream getOutput(){ + return output; + } + + private void populateDefaultHeaders(){ + defaultHeaders.put("From","ajp@test.org"); + defaultHeaders.put("User-Agent","AJPClient/1.0"); + defaultHeaders.put("Accept-Language","en"); + } + + public synchronized void decrementProcessorCount(){ + processorCount--; + notifyAll(); + } + + public int getProcessorCount(){ + return processorCount; + } + + private void parse(){ + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(requests_file); + doc.getDocumentElement().normalize(); + + NodeList reqLst = doc.getElementsByTagName("request"); + NodeList commonLst=doc.getElementsByTagName("common"); + NodeList commonHeaders=null; + + if(commonLst.getLength() > 0){ + Node common=commonLst.item(0); + if(common.getNodeType() == Node.ELEMENT_NODE){ + Element commonElmnt=(Element)common; + commonHeaders=commonElmnt.getElementsByTagName("header"); + } + } + + for(int i=0;i< reqLst.getLength();i++){ + Node request=reqLst.item(i); + if (request.getNodeType() == Node.ELEMENT_NODE) { + + RequestContext ctx=new RequestContext(this); + + //Process request URL + Element reqElmnt = (Element) request; + Node url = reqElmnt.getElementsByTagName("url").item(0); + ctx.setUrl(new URL(url.getFirstChild().getTextContent())); + + //Process common headers. May be overridden later. + if(commonHeaders != null){ + for(int j=0;j 0){ + Node headers=headersLst.item(0); + if(headers.getNodeType() == Node.ELEMENT_NODE){ + Element headersElmnt=(Element)headers; + NodeList headerLst=headersElmnt.getElementsByTagName("header"); + + for(int j=0;j< headerLst.getLength();j++){ + Node header=reqLst.item(i); + if (header.getNodeType() == Node.ELEMENT_NODE) { + Element headerElmnt=(Element)header; + String headerName=headerElmnt.getElementsByTagName("name").item(0).getFirstChild().getTextContent(); + String value=headerElmnt.getElementsByTagName("value").item(0).getFirstChild().getTextContent(); + + ctx.setHeader(headerName, value); + } + } + } + } + + //Process post if present + boolean postPresent=false; + NodeList postLst=reqElmnt.getElementsByTagName("post"); + if(postLst.getLength() > 0){ + Node post=postLst.item(0); + postPresent=true; + ctx.setMethod("POST"); + + if(post.getNodeType() == Node.ELEMENT_NODE){ + Element postElmnt=(Element)post; + + //Process bodyfile + boolean filePresent=false; + NodeList fileLst=postElmnt.getElementsByTagName("bodyfile"); + if(fileLst.getLength() > 0){ + String bodyFile=fileLst.item(0).getFirstChild().getTextContent(); + ctx.setBodyFile(new File(bodyFile)); + filePresent=true; + } + + //Process query + if(!filePresent){ // Gives the precedence to the file if both and tags are present. + NodeList queryLst=postElmnt.getElementsByTagName("query"); + if(queryLst.getLength() > 0){ + Node query=queryLst.item(0); + if(query.getNodeType() == Node.ELEMENT_NODE){ + Element queryElmnt=(Element)query; + NodeList paramLst=queryElmnt.getElementsByTagName("param"); + + for(int k=0;k 0){ + Node get=getLst.item(0); + ctx.setMethod("GET"); + + if(get.getNodeType() == Node.ELEMENT_NODE){ + Element getElmnt=(Element)get; + NodeList queryLst=getElmnt.getElementsByTagName("query"); + + if(queryLst.getLength() > 0){ + Node query=queryLst.item(0); + if(query.getNodeType() == Node.ELEMENT_NODE){ + Element queryElmnt=(Element)query; + NodeList paramLst=queryElmnt.getElementsByTagName("param"); + + for(int k=0;k 0 ){ + Node rounds=roundsLst.item(0); + String roundsStr=rounds.getFirstChild().getTextContent(); + ctx.setRounds(Integer.parseInt(roundsStr)); + } + + ctxs.add(ctx); + + //Default method is GET if no specific method is set + if(ctx.getMethod() == null){ + ctx.setMethod(Constants.GET); + } + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + } +} Index: Constants.java =================================================================== --- Constants.java (revision 0) +++ Constants.java (revision 0) @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.coyote.ajp.client; + +public class Constants { + + public static final char NEWLINE = '\n'; + public static final String COLON_SPACE = ": ";//$NON-NLS-1$ + public static final String POST="POST"; + public static final String GET="GET"; + public static final String PROTOCOL_HTTPS="https"; + public static final String HTTP_1_1="HTTP/1.1"; + + static final int AJP_HEADER_BASE = 0xA000; + + static final int MAX_SEND_SIZE = 8*1024 - 4 - 4; + + static final String APPLICATION_X_WWW_FORM_URLENCODED="application/x-www-form-urlencoded "; + +} Index: RequestContext.java =================================================================== --- RequestContext.java (revision 0) +++ RequestContext.java (revision 0) @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.coyote.ajp.client; + +import java.io.File; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +public class RequestContext { + + private URL url; + private Map headers=new HashMap(); + private Map queries=new HashMap(); + String method; + String body; + File bodyFile; + ClientContext cc; + int rounds; + + public RequestContext(ClientContext cc){ + this.cc = cc; + rounds=1; + } + + void setUrl(URL url) { + this.url = url; + } + + URL getUrl() { + return url; + } + + + void setHeader(String name,String value){ + headers.put(name, value); + } + + void setHeaderIfNotPresent(String name,String value){ + if(headers.get(name) == null){ + headers.put(name, value); + } + } + + void setQueryParam(String name, String value){ + queries.put(name, value); + } + + void setBodyFile(File file){ + this.bodyFile=file; + } + + void setMethod(String method){ + this.method=method; + } + + void setRounds(int rounds){ + this.rounds=rounds; + } + + Map getHeaders() { + return headers; + } + + ClientContext getClientContext(){ + return cc; + } + + String getMethod(){ + return method; + } + + int getRounds(){ + return rounds; + } + + File getBodyFile(){ + return bodyFile; + } + + public Map getQueryParams(){ + return queries; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((url == null) ? 0 : url.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RequestContext other = (RequestContext) obj; + if (url == null) { + if (other.url != null) + return false; + } else if (!url.equals(other.url)) + return false; + return true; + } + + + +} Index: Statistics.java =================================================================== --- Statistics.java (revision 0) +++ Statistics.java (revision 0) @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.coyote.ajp.client; + +import java.net.URL; + + +public class Statistics { + + private URL url; + private boolean timedout; + private String dateTime; + private long time_elapsed; + private int reply_code; + + void setUrl(URL url) { + this.url = url; + } + + URL getUrl() { + return url; + } + + void setTimeout(boolean timedout) { + this.timedout = timedout; + } + + boolean isTimeout() { + return timedout; + } + + void setTimeElapsed(long time_elapsed) { + this.time_elapsed = time_elapsed; + } + + long getTimeElapsed() { + return time_elapsed; + } + + void setReplyCode(int reply_code) { + this.reply_code = reply_code; + } + + int getReplyCode() { + return reply_code; + } + + void setDateTime(String dateTime) { + this.dateTime = dateTime; + } + + String getDateTime() { + return dateTime; + } + +}