/* * 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; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.util.Locale; import javax.net.SocketFactory; /** * AJP client that is not (yet) a full AJP client implementation as it just * provides the functionality required for the unit tests. The client uses * blocking IO throughout. */ public class SimpleAjpClient { private static final int DEFAULT_AJP_PACKET_SIZE = 8192; private static final byte[] AJP_CPING; static { TesterAjpMessage ajpCping = new TesterAjpMessage(16); ajpCping.reset(); ajpCping.appendByte(Constants.JK_AJP13_CPING_REQUEST); ajpCping.end(); AJP_CPING = new byte[ajpCping.getLen()]; System.arraycopy(ajpCping.getBuffer(), 0, AJP_CPING, 0, ajpCping.getLen()); } private final int packetSize; private String host = "localhost"; private int port = -1; /* GET == 2 */ private int method = 2; private String protocol = "http"; private String uri = "/"; private String remoteAddr = "192.168.0.1"; private String remoteHost = "client.example.com"; private String serverName = "www.example.com"; private int serverPort = 80; private boolean ssl = false; private Socket socket = null; public SimpleAjpClient() { this(DEFAULT_AJP_PACKET_SIZE); } public SimpleAjpClient(int packetSize) { this.packetSize = packetSize; } public void setPort(int port) { this.port = port; } public int getPort() { return port; } public void setMethod(String method) { method = method.toUpperCase(Locale.ENGLISH); switch (method) { case "OPTIONS": this.method = 1; break; case "GET": this.method = 2; break; case "HEAD": this.method = 3; break; case "POST": this.method = 4; break; case "PUT": this.method = 5; break; case "DELETE": this.method = 6; break; case "TRACE": this.method = 7; break; case "PROPFIND": this.method = 8; break; case "PROPPATCH": this.method = 9; break; case "MKCOL": this.method = 10; break; case "COPY": this.method = 11; break; case "MOVE": this.method = 12; break; case "LOCK": this.method = 13; break; case "UNLOCK": this.method = 14; break; case "ACL": this.method = 15; break; case "REPORT": this.method = 16; break; case "VERSION-CONTROL": this.method = 17; break; case "CHECKIN": this.method = 18; break; case "CHECKOUT": this.method = 19; break; case "UNCHECKOUT": this.method = 20; break; case "SEARCH": this.method = 21; break; case "MKWORKSPACE": this.method = 22; break; case "UPDATE": this.method = 23; break; case "LABEL": this.method = 24; break; case "MERGE": this.method = 25; break; case "BASELINE-CONTROL": this.method = 26; break; case "MKACTIVITY": this.method = 27; break; default: this.method = 99; } } public String getMethod() { switch (method) { case 1: return "OPTIONS"; case 2: return "GET"; case 3: return "HEAD"; case 4: return "POST"; case 5: return "PUT"; case 6: return "DELETE"; case 7: return "TRACE"; case 8: return "PROPFIND"; case 9: return "PROPPATCH"; case 10: return "MKCOL"; case 11: return "COPY"; case 12: return "MOVE"; case 13: return "LOCK"; case 14: return "UNLOCK"; case 15: return "ACL"; case 16: return "REPORT"; case 17: return "VERSION-CONTROL"; case 18: return "CHECKIN"; case 19: return "CHECKOUT"; case 20: return "UNCHECKOUT"; case 21: return "SEARCH"; case 22: return "MKWORKSPACE"; case 23: return "UPDATE"; case 24: return "LABEL"; case 25: return "MERGE"; case 26: return "BASELINE-CONTROL"; case 27: return "MKACTIVITY"; default: return "UNKNOWN"; } } public void setProtocol(String protocol) { this.protocol = protocol; } public String getProtocol() { return protocol; } public void setUri(String uri) { this.uri = uri; } public String getUri() { return uri; } public void setRemoteAddr(String remoteAddr) { this.remoteAddr = remoteAddr; } public String getRemoteAddr() { return remoteAddr; } public void setRemoteHost(String remoteHost) { this.remoteHost = remoteHost; } public String getRemoteHost() { return remoteHost; } public void setServerName(String serverName) { this.serverName = serverName; } public String getServerName() { return serverName; } public void setServerPort(int serverPort) { this.serverPort = serverPort; } public int getServerPort() { return serverPort; } public void setSsl(boolean ssl) { this.ssl = ssl; } public boolean isSsl() { return ssl; } public void connect() throws IOException { socket = SocketFactory.getDefault().createSocket(host, port); } public void disconnect() throws IOException { socket.close(); socket = null; } /* * Create a message to request the given URL. */ public TesterAjpMessage createForwardMessage() { TesterAjpMessage message = new TesterAjpMessage(packetSize); message.reset(); // Set the header bytes message.getBuffer()[0] = 0x12; message.getBuffer()[1] = 0x34; // Code 2 for forward request message.appendByte(Constants.JK_AJP13_FORWARD_REQUEST); // HTTP method, GET = 2 message.appendByte(method); // Protocol message.appendString(protocol); // Request URI message.appendString(uri); // Client address message.appendString(remoteAddr); // Client host message.appendString(remoteHost); // Server name message.appendString(serverName); // Server port message.appendInt(serverPort); // Is ssl message.appendByte(ssl ? 0x01 : 0x00); return message; } public TesterAjpMessage createBodyMessage(byte[] data) { TesterAjpMessage message = new TesterAjpMessage(packetSize); message.reset(); // Set the header bytes message.getBuffer()[0] = 0x12; message.getBuffer()[1] = 0x34; message.appendBytes(data, 0, data.length); message.end(); return message; } /* * Sends an TesterAjpMessage to the server and returns the response message. */ public TesterAjpMessage sendMessage(TesterAjpMessage headers) throws IOException { return sendMessage(headers, null); } public TesterAjpMessage sendMessage(TesterAjpMessage headers, TesterAjpMessage body) throws IOException { // Send the headers socket.getOutputStream().write( headers.getBuffer(), 0, headers.getLen()); if (body != null) { // Send the body of present socket.getOutputStream().write( body.getBuffer(), 0, body.getLen()); } // Read the response return readMessage(); } /* * Tests the connection to the server and returns the CPONG response. */ public TesterAjpMessage cping() throws IOException { // Send the ping message socket.getOutputStream().write(AJP_CPING); // Read the response return readMessage(); } /* * Reads a message from the server. */ public TesterAjpMessage readMessage() throws IOException { InputStream is = socket.getInputStream(); TesterAjpMessage message = new TesterAjpMessage(packetSize); byte[] buf = message.getBuffer(); read(is, buf, 0, Constants.H_SIZE); int messageLength = message.processHeader(false); if (messageLength < 0) { throw new IOException("Invalid AJP message length"); } else if (messageLength == 0) { return message; } else { if (messageLength > buf.length) { throw new IllegalArgumentException("Message too long [" + Integer.valueOf(messageLength) + "] for buffer length [" + Integer.valueOf(buf.length) + "]"); } read(is, buf, Constants.H_SIZE, messageLength); return message; } } protected boolean read(InputStream is, byte[] buf, int pos, int n) throws IOException { int read = 0; int res = 0; while (read < n) { res = is.read(buf, read + pos, n - read); if (res > 0) { read += res; } else { throw new IOException("Read failed"); } } return true; } }