Author: markt Date: Sun Jul 10 22:05:01 2011 New Revision: 1144976 URL: http://svn.apache.org/viewvc?rev=1144976&view=rev Log: Add a simple AJP test.
Added: tomcat/trunk/test/org/apache/coyote/ajp/ tomcat/trunk/test/org/apache/coyote/ajp/SimpleAjpClient.java tomcat/trunk/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java tomcat/trunk/test/org/apache/coyote/ajp/TesterAjpMessage.java Added: tomcat/trunk/test/org/apache/coyote/ajp/SimpleAjpClient.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/ajp/SimpleAjpClient.java?rev=1144976&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/coyote/ajp/SimpleAjpClient.java (added) +++ tomcat/trunk/test/org/apache/coyote/ajp/SimpleAjpClient.java Sun Jul 10 22:05:01 2011 @@ -0,0 +1,184 @@ +/* + * 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 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 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 String host = "localhost"; + private int port = -1; + private Socket socket = null; + + public void setPort(int port) { + this.port = port; + } + + public int getPort() { + return port; + } + + 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(String url) { + TesterAjpMessage message = new TesterAjpMessage(AJP_PACKET_SIZE); + 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(0x02); + + // Protocol + message.appendString("http"); + + // Request URI + message.appendString(url); + + // Remote address + message.appendString("10.0.0.1"); + + // Remote host + message.appendString("client.dev.local"); + + // Server name + message.appendString(host); + + // Port + message.appendInt(port); + + // Is ssl + message.appendByte(0x00); + + // No other headers or attributes + message.appendInt(0); + + // Terminator + message.appendByte(0xFF); + + // End the message and set the length + message.end(); + + return message; + } + + /** + * Sends an TesterAjpMessage to the server and returns the response message. + */ + public TesterAjpMessage sendMessage(TesterAjpMessage message) + throws IOException { + // Send the message + socket.getOutputStream().write( + message.getBuffer(), 0, message.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(AJP_PACKET_SIZE); + + byte[] buf = message.getBuffer(); + int headerLength = message.getHeaderLength(); + + read(is, buf, 0, headerLength); + + int messageLength = message.processHeader(); + 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, headerLength, 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; + } +} Added: tomcat/trunk/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java?rev=1144976&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java (added) +++ tomcat/trunk/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java Sun Jul 10 22:05:01 2011 @@ -0,0 +1,172 @@ +/* + * 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 org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; + +public class TestAbstractAjpProcessor extends TomcatBaseTest { + + @Override + protected String getProtocol() { + /* + * The tests are all setup for HTTP so need to convert the protocol + * values to AJP. + */ + // Has a protocol been specified + String protocol = System.getProperty("tomcat.test.protocol"); + + // Use BIO by default + if (protocol == null) { + protocol = "org.apache.coyote.ajp.AjpProtocol"; + } else if (protocol.contains("Nio")) { + protocol = "org.apache.coyote.ajp.AjpNioProtocol"; + } else if (protocol.contains("Apr")) { + protocol = "org.apache.coyote.ajp.AjpAprProtocol"; + } else { + protocol = "org.apache.coyote.ajp.AjpProtocol"; + } + + return protocol; + } + + public void testKeepAlive() throws Exception { + Tomcat tomcat = getTomcatInstance(); + tomcat.start(); + + // Must have a real docBase - just use temp + org.apache.catalina.Context ctx = + tomcat.addContext("", System.getProperty("java.io.tmpdir")); + Tomcat.addServlet(ctx, "helloWorld", new HelloWorldServlet()); + ctx.addServletMapping("/", "helloWorld"); + + SimpleAjpClient ajpClient = new SimpleAjpClient(); + + ajpClient.setPort(getPort()); + + ajpClient.connect(); + + validateCpong(ajpClient.cping()); + + TesterAjpMessage forwardMessage = ajpClient.createForwardMessage("/"); + + // Two requests + for (int i = 0; i < 2; i++) { + TesterAjpMessage responseHeaders = ajpClient.sendMessage(forwardMessage); + // Expect 3 packets: headers, body, end + validateResponseHeaders(responseHeaders, 200); + TesterAjpMessage responseBody = ajpClient.readMessage(); + validateResponseBody(responseBody, HelloWorldServlet.RESPONSE_TEXT); + validateResponseEnd(ajpClient.readMessage(), true); + + // Double check the connection is still open + validateCpong(ajpClient.cping()); + } + + ajpClient.disconnect(); + } + + /** + * Process response header packet and checks the status. Any other data is + * ignored. + */ + private void validateResponseHeaders(TesterAjpMessage message, + int expectedStatus) throws Exception { + // First two bytes should always be AB + assertEquals((byte) 'A', message.buf[0]); + assertEquals((byte) 'B', message.buf[1]); + + // Set the start position and read the length + message.processHeader(); + + // Check the length + assertTrue(message.len > 0); + + // Should be a header message + assertEquals(0x04, message.readByte()); + + // Check status + assertEquals(expectedStatus, message.readInt()); + + // Read the status message + message.readString(); + + // Get the number of headers + int headerCount = message.readInt(); + + for (int i = 0; i < headerCount; i++) { + // Read the header name + message.readHeaderName(); + // Read the header value + message.readString(); + } + } + + /** + * Validates that the response message is valid and contains the expected + * content. + */ + private void validateResponseBody(TesterAjpMessage message, + String expectedBody) throws Exception { + assertEquals((byte) 'A', message.buf[0]); + assertEquals((byte) 'B', message.buf[1]); + + // Set the start position and read the length + message.processHeader(); + + // Should be a body chunk message + assertEquals(0x03, message.readByte()); + + int len = message.readInt(); + assertTrue(len > 0); + String body = message.readString(len); + + assertEquals(expectedBody, body); + } + + private void validateResponseEnd(TesterAjpMessage message, + boolean expectedReuse) { + assertEquals((byte) 'A', message.buf[0]); + assertEquals((byte) 'B', message.buf[1]); + + message.processHeader(); + + // Should be an end body message + assertEquals(0x05, message.readByte()); + + // Check the length + assertEquals(2, message.getLen()); + + boolean reuse = false; + if (message.readByte() > 0) { + reuse = true; + } + + assertEquals(expectedReuse, reuse); + } + + private void validateCpong(TesterAjpMessage message) throws Exception { + // First two bytes should always be AB + assertEquals((byte) 'A', message.buf[0]); + assertEquals((byte) 'B', message.buf[1]); + // CPONG should have a message length of 1 + // This effectively checks the next two bytes + assertEquals(1, message.getLen()); + // Data should be the value 9 + assertEquals(9, message.buf[4]); + } +} Added: tomcat/trunk/test/org/apache/coyote/ajp/TesterAjpMessage.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/ajp/TesterAjpMessage.java?rev=1144976&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/coyote/ajp/TesterAjpMessage.java (added) +++ tomcat/trunk/test/org/apache/coyote/ajp/TesterAjpMessage.java Sun Jul 10 22:05:01 2011 @@ -0,0 +1,70 @@ +/* + * 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; + +/** + * Extends {@link AjpMessage} to provide additional methods for writing to the + * message. + * TODO: See if it makes sense for any/all of these methods to be transferred to + * AjpMessage + */ +public class TesterAjpMessage extends AjpMessage { + + public TesterAjpMessage(int packetSize) { + super(packetSize); + } + + public byte readByte() { + return buf[pos++]; + } + + public int readInt() { + int val = (buf[pos++] & 0xFF ) << 8; + val += buf[pos++] & 0xFF; + return val; + } + + public String readString() { + int len = readInt(); + return readString(len); + } + + public String readString(int len) { + StringBuilder buffer = new StringBuilder(len); + + for (int i = 0; i < len; i++) { + char c = (char) buf[pos++]; + buffer.append(c); + } + // Read end of string marker + readByte(); + + return buffer.toString(); + } + + public String readHeaderName() { + byte b = readByte(); + if ((b & 0xFF) == 0xA0) { + // Coded header + return Constants.getResponseHeaderForCode(readByte()); + } else { + int len = (b & 0xFF) << 8; + len += getByte() & 0xFF; + return readString(len); + } + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org