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

Reply via email to