jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dpfis...@apache.org
Subject svn commit: r1170211 - in /jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk: ./ client/ server/ util/
Date Tue, 13 Sep 2011 15:34:25 GMT
Author: dpfister
Date: Tue Sep 13 15:34:24 2011
New Revision: 1170211

URL: http://svn.apache.org/viewvc?rev=1170211&view=rev
Log:
HTTP interface to microkernel
- add client

Added:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java   (with props)
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/HttpExecutor.java   (with props)
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Request.java   (with props)
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java
      - copied, changed from r1170072, jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/SocketConnection.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/BoundedInputStream.java   (with props)
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/ChunkedInputStream.java   (with props)
Modified:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Request.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Response.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Server.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/SocketConnection.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/IOUtils.java

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java?rev=1170211&r1=1170210&r2=1170211&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/MicroKernelFactory.java Tue Sep 13 15:34:24 2011
@@ -17,9 +17,13 @@
 package org.apache.jackrabbit.mk;
 
 import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.client.Client;
 import org.apache.jackrabbit.mk.mem.MemoryKernelImpl;
 
 import java.io.File;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
 
 /**
  * A factory to create a MicroKernel instance.
@@ -59,6 +63,13 @@ public class MicroKernelFactory {
                 rm(new File(dir, ".mk"));
             }
             return new MicroKernelImpl(dir);
+        } else if (url.startsWith("http:")) {
+            try {
+                URI uri = new URI(url);
+                return new Client(new InetSocketAddress(uri.getHost(), uri.getPort()));
+            } catch (URISyntaxException e) {
+                throw new IllegalArgumentException(e.getMessage());
+            }
         } else {
             throw new IllegalArgumentException(url);
         }

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java?rev=1170211&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java Tue Sep 13 15:34:24 2011
@@ -0,0 +1,259 @@
+/*
+ * 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.jackrabbit.mk.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.mk.api.MicroKernelException;
+import org.apache.jackrabbit.mk.util.IOUtils;
+
+/**
+ * Client exposing a <code>MicroKernel</code> interface, that "remotes" commands
+ * to a server.
+ */
+public class Client implements MicroKernel {
+    
+    private static final String MK_EXCEPTION_PREFIX = MicroKernelException.class.getName() + ":";  
+    
+    private final InetSocketAddress addr;
+    
+    private boolean disposed;
+    
+    /**
+     * Create a new instance of this class.
+     * 
+     * @param addr socket address
+     */
+    public Client(InetSocketAddress addr) {
+        this.addr = addr;
+    }
+
+    //-------------------------------------------------- implements MicroKernel
+    
+    public void dispose() {
+        disposed = true;
+    }
+
+    public String getHeadRevision() throws MicroKernelException {
+        Request request = null;
+        
+        try {
+            request = createRequest("getHeadRevision");
+            return request.getString();
+        } catch (IOException e) {
+            throw new MicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
+    public String getRevisions(long since, int maxEntries)
+            throws MicroKernelException {
+
+        Request request = null;
+
+        try {
+            request = createRequest("getRevisions");
+            request.addParameter("since", since);
+            request.addParameter("max_entries", maxEntries);
+            return request.getString();
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
+    public String waitForCommit(String oldHeadRevision, long maxWaitMillis)
+            throws MicroKernelException, InterruptedException {
+
+        Request request = null;
+
+        try {
+            request = createRequest("waitForCommit");
+            request.addParameter("revision_id", oldHeadRevision);
+            request.addParameter("max_wait_millis", maxWaitMillis);
+            return request.getString();
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
+    public String getJournal(String fromRevisionId, String toRevisionId)
+            throws MicroKernelException {
+        
+        Request request = null;
+
+        try {
+            request = createRequest("getJournal");
+            request.addParameter("from_revision_id", fromRevisionId);
+            request.addParameter("to_revision_id", toRevisionId);
+            return request.getString();
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
+    public boolean nodeExists(String path, String revisionId)
+            throws MicroKernelException {
+
+        Request request = null;
+
+        try {
+            request = createRequest("nodeExists");
+            request.addParameter("path", path);
+            request.addParameter("revision_id", revisionId);
+            return request.getBoolean();
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
+    public String getNodes(String path, String revisionId)
+            throws MicroKernelException {
+
+        return getNodes(path, revisionId, 1, 0, -1);
+    }
+
+    public String getNodes(String path, String revisionId, int depth,
+            long offset, int count) throws MicroKernelException {
+        
+        Request request = null;
+
+        try {
+            request = createRequest("getNodes");
+            request.addParameter("path", path);
+            request.addParameter("revision_id", revisionId);
+            request.addParameter("depth", depth);
+            request.addParameter("offset", offset);
+            request.addParameter("count", count);
+            return request.getString();
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
+    public String commit(String path, String jsonDiff, String revisionId, String message)
+            throws MicroKernelException {
+        
+        Request request = null;
+
+        try {
+            request = createRequest("commit");
+            request.addParameter("path", path);
+            request.addParameter("json_diff", jsonDiff);
+            request.addParameter("revision_id", revisionId);
+            request.addParameter("message", message);
+            return request.getString();
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
+    public long getLength(String blobId) throws MicroKernelException {
+        Request request = null;
+
+        try {
+            request = createRequest("getLength");
+            request.addParameter("blob_id", blobId);
+            return request.getLong();
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
+    public int read(String blobId, long pos, byte[] buff, int off, int length)
+            throws MicroKernelException {
+
+        Request request = null;
+
+        try {
+            request = createRequest("read");
+            request.addParameter("blob_id", blobId);
+            request.addParameter("pos", pos);
+            request.addParameter("length", length);
+            return request.getBytes(buff, off, length);
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+
+    public String write(InputStream in) throws MicroKernelException {
+        Request request = null;
+
+        try {
+            request = createRequest("write");
+            request.addFileParameter("file", in);
+            return request.getString();
+        } catch (IOException e) {
+            throw toMicroKernelException(e);
+        } finally {
+            IOUtils.closeQuietly(request);
+        }
+    }
+    
+    /**
+     * Convert an I/O exception into a MicroKernelException, possibly by 
+     * unwrapping an already wrapped MicroKernelException.
+     * 
+     * @param e I/O exception 
+     * @return MicroKernelException
+     */
+    private MicroKernelException toMicroKernelException(IOException e) {
+        String msg = e.getMessage();
+        if (msg != null && msg.startsWith(MK_EXCEPTION_PREFIX)) {
+            return new MicroKernelException(msg.substring(MK_EXCEPTION_PREFIX.length()).trim());
+        }
+        return new MicroKernelException(e);
+    }
+
+    /**
+     * Create a request for the given command to be executed.
+     * 
+     * @param command command name
+     * @return request
+     * @throws IOException if an I/O error occurs
+     * @throws MicroKernelException if an exception occurs
+     * 
+     * TODO reuse executors and/or requests
+     */
+    private Request createRequest(String command) throws IOException, MicroKernelException {
+        if (disposed) {
+            throw new IllegalStateException("This instance has already been disposed");
+        }
+        Socket socket = new Socket(addr.getAddress(), addr.getPort());
+        return new Request(new HttpExecutor(socket), command);
+    }
+}

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Client.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/HttpExecutor.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/HttpExecutor.java?rev=1170211&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/HttpExecutor.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/HttpExecutor.java Tue Sep 13 15:34:24 2011
@@ -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.jackrabbit.mk.client;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.URLEncoder;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.jackrabbit.mk.util.BoundedInputStream;
+import org.apache.jackrabbit.mk.util.ChunkedInputStream;
+import org.apache.jackrabbit.mk.util.IOUtils;
+
+/**
+ * Executes commands as HTTP requests.
+ */
+class HttpExecutor implements Closeable {
+
+    private final Socket socket;
+    
+    private InputStream socketIn;
+    
+    private OutputStream socketOut;
+    
+    private final byte[] chunkLen = new byte[4];
+
+    /**
+     * Create a new instance of this class.
+     * 
+     * @param socket socket
+     */
+    public HttpExecutor(Socket socket) {
+        this.socket = socket;
+    }
+    
+    /**
+     * Execute a request.
+     * 
+     * @param command command to execute
+     * @param params arguments to command
+     * @param in bytes to pass
+     * @return result input stream
+     * 
+     * @throws IOException if an I/O error occurs
+     */
+    public InputStream execute(String command, Map<String, String> params, InputStream in)
+            throws IOException {
+        
+        // send request
+        socketOut = new BufferedOutputStream(socket.getOutputStream());
+
+        String contentType = "application/x-www-form-urlencoded";
+        if (in != null) {
+            contentType = "multipart/form-data";
+        }
+        
+        writeLine(String.format("POST /%s HTTP/1.1", command));
+        writeLine(String.format("Content-Type: %s", contentType));
+        writeLine("Transfer-Encoding: chunked");
+        writeLine("");
+        
+        if (in != null) {
+            // TODO sendInputStream(in);
+        } else {
+            for (Map.Entry<String,String> param : params.entrySet()) {
+                String s = String.format("%s=%s&", 
+                        URLEncoder.encode(param.getKey(), "8859_1"),
+                        URLEncoder.encode(param.getValue(), "8859_1"));
+                writeChunk(s.getBytes());
+            }
+        }
+        
+        writeChunk(new byte[0]);
+        socketOut.flush();
+        
+        // read response
+        socketIn = new BufferedInputStream(socket.getInputStream());
+        
+        String responseLine = readLine(socketIn);
+        String[] parts = responseLine.split(" ");
+        if (parts.length < 3) {
+            String msg = String.format("Malformed HTTP response line: %s", responseLine);
+            throw new IOException(msg);
+        }
+        
+        int statusCode;
+        
+        try {
+            statusCode = Integer.parseInt(parts[1]);
+        } catch (RuntimeException e) {
+            String msg = String.format("Malformed HTTP response line: %s", responseLine);
+            throw new IOException(msg);
+        }
+        
+        Map<String, String> headers = new LinkedHashMap<String, String>();
+        
+        for (;;) {
+            String headerLine = readLine(socketIn);
+            if (headerLine.length() == 0) {
+                break;
+            }
+            parts = headerLine.split(":");
+            if (parts.length == 2) {
+                headers.put(parts[0].trim(), parts[1].trim());
+            }
+        }
+
+        InputStream reqIn;
+        
+        String encoding = headers.get("Transfer-Encoding");
+        if ("chunked".equalsIgnoreCase(encoding)) {
+            reqIn = new ChunkedInputStream(socketIn);
+        } else {
+            int contentLength = -1;
+            
+            String s = headers.get("Content-Length");
+            if (s != null) {
+                try {
+                    contentLength = Integer.parseInt(s);
+                } catch (RuntimeException e) {
+                    /* ignore */
+                }
+            }
+            if (contentLength == -1) {
+                contentLength = 0;
+            }
+            reqIn = new BoundedInputStream(socketIn, contentLength);
+        }
+        
+        switch (statusCode) {
+        case 200:
+            return reqIn;
+        case 500:
+            throw new IOException(readLine(reqIn));
+        default:
+            String msg = String.format("HTTP request failed with status code: %d", statusCode);
+            throw new IOException(msg);
+        }
+    }
+
+    public void close() {
+        IOUtils.closeQuietly(socketOut);
+        IOUtils.closeQuietly(socketIn);
+        IOUtils.closeQuietly(socket);
+    }
+
+    /**
+     * Write a request header.
+     * 
+     * @param s line
+     * @throws IOException if an I/O error occurs
+     */
+    private void writeLine(String s) throws IOException {
+        socketOut.write(s.getBytes());
+        socketOut.write("\r\n".getBytes());
+    }
+
+    /**
+     * Write a chunk of bytes in the request body. 
+     * 
+     * @param b bytes
+     * @throws IOException if an I/O error occurs
+     */
+    private void writeChunk(byte[] b) throws IOException {
+        int off = 0;
+        
+        do {
+            int len = Math.min(b.length - off, 65534);
+            
+            toHexString(len, chunkLen);
+            socketOut.write(chunkLen);
+            socketOut.write("\r\n".getBytes());
+            socketOut.write(b, off, len);
+            socketOut.write("\r\n".getBytes());
+            
+            off += len;
+            
+        } while (off < b.length);
+    }
+    
+    /**
+     * Read a single line, terminated by a CR LF combination from an input.
+     * 
+     * @return line
+     * @throws IOException if an I/O error occurs
+     */
+    private static String readLine(InputStream in) throws IOException {
+        StringBuilder line = new StringBuilder(128);
+        
+        for (;;) {
+            int c = in.read();
+            switch (c) {
+            case '\r':
+                // swallow
+                break;
+            case '\n':
+                return line.toString();
+            case -1:
+                throw new EOFException();
+            default:
+                line.append((char) c);
+            }
+        }
+    }
+
+    /**
+     * Convert an integer into a byte array, consisting of its hexadecimal
+     * representation.
+     *
+     * @param n integer
+     * @param b byte array
+     */
+    private static void toHexString(int n, byte[] b) {
+        for (int i = b.length - 1; i >= 0; i--) {
+            int c = n & 0x0f;
+            if (c >= 0 && c <= 9) {
+                c += '0';
+            } else {
+                c += 'A' - 10;
+            }
+            b[i] = (byte) c;
+            n >>= 4;
+        }
+    }
+}

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/HttpExecutor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/HttpExecutor.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Request.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Request.java?rev=1170211&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Request.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Request.java Tue Sep 13 15:34:24 2011
@@ -0,0 +1,129 @@
+/*
+ * 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.jackrabbit.mk.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.jackrabbit.mk.util.IOUtils;
+
+/**
+ * Contains the details of a request to some remote <code>MicroKernel</code>
+ * implementation.
+ */
+class Request implements Closeable {
+    
+    private final HttpExecutor executor;
+    
+    private final String command;
+    
+    private final Map<String, String> params = new LinkedHashMap<String, String>();
+    
+    private InputStream in;
+    
+    private InputStream resultIn;
+    
+    private boolean executed;
+    
+    /**
+     * Create a new instance of this class.
+     * 
+     * @param executor executor
+     * @param command command name
+     */
+    public Request(HttpExecutor executor, String command) {
+        this.executor = executor;
+        this.command = command;
+    }
+    
+    public void addParameter(String name, String value) {
+        if (value != null) {
+            params.put(name, value);
+        }
+    }
+
+    public void addParameter(String name, int value) {
+        params.put(name, String.valueOf(value));
+    }
+    
+    public void addParameter(String name, long value) {
+        params.put(name, String.valueOf(value));
+    }
+    
+    public void addFileParameter(String name, InputStream in) {
+        this.in = in;
+    }
+    
+    public void execute() throws IOException {
+        if (executed) {
+            return;
+        }
+        try {
+            resultIn = executor.execute(command, params, in);
+        } finally {
+            executed = true;
+        }
+    }
+    
+    public String getString() throws IOException {
+        execute();
+        
+        return new String(toByteArray(resultIn), "8859_1");
+    }
+
+    public boolean getBoolean() throws IOException {
+        execute();
+        
+        return Boolean.parseBoolean(getString());
+    }
+    
+    public long getLong() throws IOException {
+        execute();
+        
+        return Long.parseLong(getString());
+    }
+
+    public int getBytes(byte[] b, int off, int len) throws IOException {
+        execute();
+        
+        int count = 0;
+        while (count < len) {
+            int n = resultIn.read(b, off + count, len - count);
+            if (n < 0) {
+                break;
+            }
+            count += n;
+        }
+        return count == 0 && len != 0 ? -1 : count;
+    }
+    
+    public void close() {
+        // TODO set conn reference to null and reuse it
+        
+        executor.close();
+    }
+    
+    private static byte[] toByteArray(InputStream in) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
+        IOUtils.copy(in, out);
+        return out.toByteArray();
+    }
+}

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Request.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/client/Request.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java?rev=1170211&r1=1170210&r2=1170211&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java Tue Sep 13 15:34:24 2011
@@ -20,31 +20,50 @@ import java.io.InputStream;
 import java.io.IOException;
 
 /**
- * Stream that reads bytes until a given string boundary, preceded
+ * Stream that reads bytes until it sees a given string boundary, preceded
  * by CR+LF, as used in multipart/form-data.
  */
-public class BoundaryInputStream extends InputStream {
+class BoundaryInputStream extends InputStream {
 
     private InputStream in;
+    
     private final byte[] boundary;
+    
     private final byte[] buf;
+    
     private int offset;
+    
     private int count;
+    
     private int boundaryIndex;
+    
     private boolean eos;
 
+    /**
+     * Create a new instance of this class.
+     * 
+     * @param in base input
+     * @param boundary boundary
+     */
     public BoundaryInputStream(InputStream in, String boundary) {
         this(in, boundary, 8192);
     }
 
+    /**
+     * Create a new instance of this class.
+     * 
+     * @param in base input
+     * @param boundary boundary
+     * @param size size of internal read-ahead buffer
+     */
     public BoundaryInputStream(InputStream in, String boundary, int size) {
-        /* Must be able to unread this many bytes */
-        if (size < boundary.length() + 3) {
-            size = boundary.length() + 3;
-        }
         this.in = in;
         this.boundary = ("\r\n" + boundary).getBytes();
 
+        // Must be able to unread this many bytes
+        if (size < this.boundary.length + 1) {
+            size = this.boundary.length + 1;
+        }
         buf = new byte[size];
     }
 

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java?rev=1170211&r1=1170210&r2=1170211&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java Tue Sep 13 15:34:24 2011
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.mk.server;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.HashMap;
 
 import org.apache.jackrabbit.mk.util.IOUtils;
 
@@ -34,9 +35,13 @@ class FileServlet implements Servlet {
         InputStream in = FileServlet.class.getResourceAsStream(file.substring(1));
         if (in != null) {
             try {
-                int ext = file.lastIndexOf('.');
-                if (ext != -1) {
-                    response.setContentTypeByExtension(file.substring(ext + 1));
+                int dotIndex = file.lastIndexOf('.');
+                if (dotIndex != -1) {
+                    String contentType = MIME_TYPES.get(file.substring(dotIndex + 1));
+                    if (contentType == null) {
+                        contentType = "application/octet-stream";
+                    }
+                    response.setContentType(contentType);
                 }
                 IOUtils.copy(in, response.getOutputStream());
             } finally {
@@ -46,4 +51,15 @@ class FileServlet implements Servlet {
             response.setStatusCode(404);
         }
     }
+
+    /* Mime types table */
+    private static final HashMap<String, String> MIME_TYPES = new HashMap<String, String>();
+    
+    static {
+        MIME_TYPES.put("html", "text/html");
+        MIME_TYPES.put("css",  "text/css");
+        MIME_TYPES.put("js",   "application/javascript");
+        MIME_TYPES.put("json", "application/json");
+        MIME_TYPES.put("png",  "image/png");
+    }
 }

Copied: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java (from r1170072, jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/SocketConnection.java)
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java?p2=jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java&p1=jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/SocketConnection.java&r1=1170072&r2=1170211&rev=1170211&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/SocketConnection.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java Tue Sep 13 15:34:24 2011
@@ -28,21 +28,24 @@ import org.apache.jackrabbit.mk.util.IOU
 /**
  * Process all HTTP requests on a single socket.
  */
-class SocketConnection {
+class HttpProcessor {
 
-    private final Socket sock;
+    private final Socket socket;
+    
     private final Servlet servlet;
-    private InputStream sockIn;
-    private OutputStream sockOut;
+    
+    private InputStream socketIn;
+    
+    private OutputStream socketOut;
 
     /**
      * Create a new instance of this class.
      * 
-     * @param sock socket
+     * @param socket socket
      * @param servlet servlet to invoke for incoming requests
      */
-    public SocketConnection(Socket sock, Servlet servlet) {
-        this.sock = sock;
+    public HttpProcessor(Socket socket, Servlet servlet) {
+        this.socket = socket;
         this.servlet = servlet;
     }
     
@@ -53,18 +56,18 @@ class SocketConnection {
      */
     public void process() throws IOException {
         try { 
-            sockIn = new BufferedInputStream(sock.getInputStream());
-            sockOut = new BufferedOutputStream(sock.getOutputStream());
+            socketIn = new BufferedInputStream(socket.getInputStream());
+            socketOut = new BufferedOutputStream(socket.getOutputStream());
             
             do {
                 process1();
                 
-            } while (false /* TODO: check for keep-alive */);
+            } while (false /* TODO: check for keep-alive and reuse */);
         
         }  finally {
-            IOUtils.closeQuietly(sockOut);
-            IOUtils.closeQuietly(sockIn);
-            IOUtils.closeQuietly(sock);
+            IOUtils.closeQuietly(socketOut);
+            IOUtils.closeQuietly(socketIn);
+            IOUtils.closeQuietly(socket);
         }
     }
     
@@ -75,20 +78,18 @@ class SocketConnection {
      */
     private void process1() throws IOException {
         Request request = null;
-        Response response = new Response(sockOut);
+        Response response = new Response(socketOut);
         
         try {
-            request = Request.parse(sockIn);
+            request = Request.parse(socketIn);
             if (request == null) {
                 response.setStatusCode(400);
             } else {
                 servlet.service(request, response);
             }
         } finally {
-            if (request != null) {
-                request.finish();
-            }
-            response.finish();
+            IOUtils.closeQuietly(request);
+            IOUtils.closeQuietly(response);
         }
     }
     

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java?rev=1170211&r1=1170210&r2=1170211&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java Tue Sep 13 15:34:24 2011
@@ -70,9 +70,8 @@ class MicroKernelServlet implements Serv
     
     private static interface Command {
         
-        public void execute(MicroKernel mk, Request request, Response response)
+        void execute(MicroKernel mk, Request request, Response response)
                 throws IOException, MicroKernelException;
-        
     }
     
     private static final Map<String, Command> COMMANDS = new HashMap<String, Command>();
@@ -96,7 +95,7 @@ class MicroKernelServlet implements Serv
                 throws IOException, MicroKernelException {
 
             response.setContentType("text/plain");
-            response.getOutputStream().write(mk.getHeadRevision().getBytes());
+            response.write(mk.getHeadRevision());
         }        
     }
 
@@ -108,9 +107,12 @@ class MicroKernelServlet implements Serv
             long since = request.getParameter("since", 0L);
             int maxEntries = request.getParameter("max_entries", 10);
 
-            response.setContentType("text/plain");
+            response.setContentType("application/json");
             String json = mk.getRevisions(since, maxEntries);
-            response.getOutputStream().write(JsopBuilder.prettyPrint(json).getBytes());
+            if (request.getHeaders().containsKey("User-Agent")) {
+                json = JsopBuilder.prettyPrint(json);
+            }
+            response.write(json);
         }        
     }
     
@@ -133,7 +135,7 @@ class MicroKernelServlet implements Serv
             }
 
             response.setContentType("text/plain");
-            response.getOutputStream().write(currentHead.getBytes());
+            response.write(currentHead);
         }
     }
 
@@ -147,9 +149,12 @@ class MicroKernelServlet implements Serv
             String fromRevisionId = request.getParameter("from_revision_id", headRevision);
             String toRevisionId = request.getParameter("to_revision_id", headRevision);
 
-            response.setContentType("text/plain");
+            response.setContentType("application/json");
             String json = mk.getJournal(fromRevisionId, toRevisionId);
-            response.getOutputStream().write(JsopBuilder.prettyPrint(json).getBytes());
+            if (request.getHeaders().containsKey("User-Agent")) {
+                json = JsopBuilder.prettyPrint(json);
+            }
+            response.write(json);
         }        
     }
 
@@ -164,7 +169,7 @@ class MicroKernelServlet implements Serv
             String revisionId = request.getParameter("revision_id", headRevision);
 
             response.setContentType("text/plain");
-            response.getOutputStream().write(Boolean.toString(mk.nodeExists(path, revisionId)).getBytes());
+            response.write(Boolean.toString(mk.nodeExists(path, revisionId)));
         }
     }
 
@@ -181,9 +186,12 @@ class MicroKernelServlet implements Serv
             long offset = request.getParameter("offset", 0L);
             int count = request.getParameter("count", -1);
             
-            response.setContentType("text/plain");
+            response.setContentType("application/json");
             String json = mk.getNodes(path, revisionId, depth, offset, count);
-            response.getOutputStream().write(JsopBuilder.prettyPrint(json).getBytes());
+            if (request.getHeaders().containsKey("User-Agent")) {
+                json = JsopBuilder.prettyPrint(json);
+            }
+            response.write(json);
         }        
     }
 
@@ -202,7 +210,7 @@ class MicroKernelServlet implements Serv
             String newRevision = mk.commit(path, jsonDiff, revisionId, message);
 
             response.setContentType("text/plain");
-            response.getOutputStream().write(newRevision.getBytes());
+            response.write(newRevision);
         }        
     }
 
@@ -215,7 +223,7 @@ class MicroKernelServlet implements Serv
             long length = mk.getLength(blobId);
 
             response.setContentType("text/plain");
-            response.getOutputStream().write(Long.toString(length).getBytes());
+            response.write(Long.toString(length));
         }
     }
 
@@ -225,11 +233,24 @@ class MicroKernelServlet implements Serv
                 throws IOException, MicroKernelException {
 
             String blobId = request.getParameter("blob_id");
-            if (blobId != null) {
+            if (blobId == null) {
+                return;
+            }
+            long pos = request.getParameter("pos", 0L);
+            int length = request.getParameter("length", -1); 
+            
+            OutputStream out = response.getOutputStream();
+            if (pos == 0L && length == -1) {
+                /* return the complete binary */
                 InputStream in = new MicroKernelInputStream(mk, blobId);
-                OutputStream out = response.getOutputStream();
-
                 IOUtils.copy(in, out);
+            } else {
+                /* return some range */
+                byte[] buff = new byte[length];
+                int count = mk.read(blobId, pos, buff, 0, length);
+                if (count > 0) {
+                    out.write(buff, 0, count);
+                }
             }
         }
     }
@@ -244,7 +265,7 @@ class MicroKernelServlet implements Serv
                 String blobId = mk.write(in);
 
                 response.setContentType("text/plain");
-                response.getOutputStream().write(blobId.getBytes());
+                response.write(blobId);
             }
         }        
     }

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Request.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Request.java?rev=1170211&r1=1170210&r2=1170211&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Request.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Request.java Tue Sep 13 15:34:24 2011
@@ -17,29 +17,46 @@
 package org.apache.jackrabbit.mk.server;
 
 import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URLDecoder;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
+import org.apache.jackrabbit.mk.util.BoundedInputStream;
+import org.apache.jackrabbit.mk.util.ChunkedInputStream;
 import org.apache.jackrabbit.mk.util.IOUtils;
 
 /**
  * HTTP Request implementation.
  */
-class Request {
+class Request implements Closeable {
 
     private InputStream in;
+    
     private final String method;
+    
     private final String file;
+    
     private String queryString;
-    private final Map<String,String> headers; 
+    
+    private final Map<String,String> headers;
+    
     private Map<String, String> params;
-    private boolean chunked;
-    private BodyInputStream reqIn;
     
+    private InputStream reqIn;
+    
+    /**
+     * Create a new instance of this class.
+     * 
+     * @param method HTTP method
+     * @param uri target URI
+     * @param headers request headers
+     * @param in request body
+     */
     private Request(String method, String uri, Map<String,String> headers, InputStream in) {
         this.method = method;
         
@@ -65,7 +82,7 @@ class Request {
         String method = parts[0];
         String uri = parts[1];
         
-        Map<String, String> headers = new HashMap<String, String>();
+        Map<String, String> headers = new LinkedHashMap<String, String>();
         
         for (;;) {
             String headerLine = readLine(in);
@@ -81,8 +98,7 @@ class Request {
     }
     
     /**
-     * Read a single line, terminated by a CR LF combination from the socket
-     * input.
+     * Read a single line, terminated by a CR LF combination from an <code>InputStream</code>.
      * 
      * @return line
      * @throws IOException if an I/O error occurs
@@ -130,6 +146,10 @@ class Request {
         return -1;
     }
     
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+    
     public String getQueryString() {
         return queryString;
     }
@@ -198,89 +218,41 @@ class Request {
             if (line.length() == 0) {
                 break;
             }
-            // TODO collect information
+            // TODO collect other information (such as mime type)
         }
-
         return new BoundaryInputStream(body, boundary);
     }
 
-    private static void collectParameters(String s, Map<String,String> map) {
+    private static void collectParameters(String s, Map<String,String> map) throws IOException {
         for (String param : s.split("&")) {
             String[] nv = param.split("=");
             if (nv.length == 2) {
-                map.put(URLDecoder.decode(nv[0]), URLDecoder.decode(nv[1]));
+                map.put(URLDecoder.decode(nv[0], "8859_1"), URLDecoder.decode(nv[1], "8859_1"));
             }
         }
     }
     
     public InputStream getInputStream() throws IOException {
         if (reqIn == null) {
-            // TODO: handle chunked input
-            int contentLength = getContentLength();
-            if (contentLength == -1) {
-                contentLength = 0;
+            String encoding = headers.get("Transfer-Encoding");
+            if ("chunked".equalsIgnoreCase(encoding)) {
+                reqIn = new ChunkedInputStream(in);
+            } else {
+                int contentLength = getContentLength();
+                if (contentLength == -1) {
+                    contentLength = 0;
+                }
+                reqIn = new BoundedInputStream(in, contentLength);
             }
-            reqIn = new BodyInputStream(contentLength);
         }
         return reqIn;
     }
     
-    private int readChunk(byte[] b, int off, int len) throws IOException {
-        if (in == null) {
-            return -1;
-        }
-        if (chunked) {
-            // TODO: handle chunked input
-            return -1;
-        } else {
-            return in.read(b, off, len);
-        }
-    }
-    
-    void finish() {
-        // TODO finish reading input stream if bytes are left
-        
-        in = null;
-    }
-    
-    /**
-     * Internal <code>InputStream</code> passed to servlet handlers.
-     */
-    private class BodyInputStream extends InputStream {
-        
-        private final int limit;
-        private int count;
-        
-        public BodyInputStream(int limit) {
-            this.limit = limit;
-        }
+    public void close() {
+        if (in != null) {
+            IOUtils.closeQuietly(reqIn);
 
-        @Override
-        public int read() throws IOException {
-            if (count < limit) {
-                byte[] b = new byte[1];
-                if (readChunk(b, 0, b.length) == 1) {
-                    count++;
-                    return b[0] & 0xff;
-                }
-            }
-            return -1;
-        }
-        
-        @Override
-        public int read(byte[] b, int off, int len) throws IOException {
-            if (count < limit) {
-                if (limit - count < len) {
-                    len = limit - count;
-                }
-                int n = readChunk(b, off, len);
-                if (n > 0) {
-                    count += n;
-                }
-                return n;
-            }
-            return -1;
+            in = null;
         }
     }
-
 }

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Response.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Response.java?rev=1170211&r1=1170210&r2=1170211&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Response.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Response.java Tue Sep 13 15:34:24 2011
@@ -16,25 +16,36 @@
  */
 package org.apache.jackrabbit.mk.server;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.HashMap;
 
 import org.apache.jackrabbit.mk.util.IOUtils;
 
 /**
  * HTTP Response implementation.
  */
-class Response {
+class Response implements Closeable {
     
     private OutputStream out;
+    
     private boolean headersSent;
+    
     private boolean committed;
+    
     private boolean chunked;
+    
     private int statusCode;
+    
     private String contentType;
+    
     private BodyOutputStream respOut;
     
+    /**
+     * Create a new instance of this class.
+     * 
+     * @param out output stream
+     */
     public Response(OutputStream out) {
         this.out = out;
     }
@@ -80,7 +91,7 @@ class Response {
                     "<h1>%s</h1>" +
                     "</body></html>", statusCode, msg, msg);
             setContentType("text/html");
-            getOutputStream().write(body.getBytes());
+            write(body);
         }
         
         writeLine(String.format("HTTP/1.1 %d %s", statusCode, msg));
@@ -100,7 +111,7 @@ class Response {
         writeLine("");
     }
 
-    public void finish() throws IOException {
+    public void close() throws IOException {
         committed = true;
         
         try {
@@ -136,25 +147,6 @@ class Response {
         this.contentType = contentType;
     }
 
-    public void setContentTypeByExtension(String ext) {
-        String contentType = MIME_TYPES.get(ext);
-        if (contentType == null) {
-            contentType = "application/octet-stream";
-        }
-        setContentType(contentType);
-    }
-
-    
-    private static final HashMap<String, String> MIME_TYPES = new HashMap<String, String>();
-    
-    static {
-        MIME_TYPES.put("html", "text/html");
-        MIME_TYPES.put("css", "text/css");
-        MIME_TYPES.put("js", "application/javascript");
-        MIME_TYPES.put("json", "application/json");
-        MIME_TYPES.put("png", "image/png");
-    }
-    
     public OutputStream getOutputStream() {
         if (respOut == null) {
             respOut = new BodyOutputStream();
@@ -166,12 +158,17 @@ class Response {
         this.statusCode = statusCode;
     }
     
+    public void write(String s) throws IOException {
+        getOutputStream().write(s.getBytes("8859_1"));
+    }
+    
     /**
      * Internal <code>OutputStream</code> passed to servlet handlers.
      */
     private class BodyOutputStream extends OutputStream {
         
         private final byte[] buf = new byte[8192];
+        
         private int count;
 
         /**

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Server.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Server.java?rev=1170211&r1=1170210&r2=1170211&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Server.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/server/Server.java Tue Sep 13 15:34:24 2011
@@ -33,26 +33,48 @@ import org.apache.jackrabbit.mk.api.Micr
 public class Server {
 
     private AtomicReference<MicroKernel> mkref;
+    
     private MicroKernelServlet mkServlet;
+    
     private AtomicBoolean started = new AtomicBoolean();
+    
     private AtomicBoolean stopped = new AtomicBoolean();
+    
     private ServerSocket ss;
+    
     private ExecutorService es;
+    
     private int port = 28080;
     
+    /**
+     * Create a new instance of this class.
+     * 
+     * @param mk micro kernel
+     */
     public Server(MicroKernel mk) {
         this.mkref = new AtomicReference<MicroKernel>(mk);
         
         mkServlet = new MicroKernelServlet(mkref);
     }
 
-    public void setPort(int port) {
+    /**
+     * Set port number to listen to.
+     * 
+     * @param port port numbern
+     * @throws IllegalStateException if the server is already started
+     */
+    public void setPort(int port) throws IllegalStateException {
         if (started.get()) {
             throw new IllegalStateException("Server already started.");
         }
         this.port = port;
     }
     
+    /**
+     * Start this server.
+     * 
+     * @throws IOException if an I/O error occurs
+     */
     public void start() throws IOException {
         if (!started.compareAndSet(false, true)) {
             return;
@@ -64,10 +86,10 @@ public class Server {
             public void run() {
                 try {
                     while (!stopped.get()) {
-                        final Socket s = ss.accept();
+                        final Socket socket = ss.accept();
                         es.execute(new Runnable() {
                             public void run() {
-                                handle(s);
+                                handle(socket);
                             }
                         });
                     }
@@ -78,21 +100,29 @@ public class Server {
         }, "Acceptor").start();
     }
     
-    void handle(Socket s) {
+    /**
+     * Handle a connection attempt by a client.
+     * 
+     * @param socket client socket
+     */
+    void handle(Socket socket) {
         try {
-            s.setTcpNoDelay(true);
+            socket.setTcpNoDelay(true);
         } catch (IOException e) {
             /* ignore */
         }
         try {
-            new SocketConnection(s, new ServletImpl()).process();
+            new HttpProcessor(socket, new ServletImpl()).process();
         } catch (IOException e) {
             /* ignore */
         }
     }
     
+    /**
+     * Internal servlet that handles all requests to this server.
+     */
     class ServletImpl implements Servlet {
-        
+
         private final Servlet fileServlet = new FileServlet();
 
         public void service(Request request, Response response)
@@ -106,6 +136,9 @@ public class Server {
         }
     }
     
+    /**
+     * Stop this server.
+     */
     public void stop() {
         if (!stopped.compareAndSet(false, true)) {
             return;

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/BoundedInputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/BoundedInputStream.java?rev=1170211&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/BoundedInputStream.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/BoundedInputStream.java Tue Sep 13 15:34:24 2011
@@ -0,0 +1,92 @@
+/*
+ * 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.jackrabbit.mk.util;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Implementation of an <code>InputStream</code> that is bounded by a limit
+ * and will return <code>-1</code> on reads when this limit is exceeded.
+ */
+public class BoundedInputStream extends FilterInputStream {
+    
+    private final int limit;
+    private int count;
+    
+    /**
+     * Create a new instance of this class.
+     *
+     * @param in input stream
+     * @param limit limit
+     */
+    public BoundedInputStream(InputStream in, int limit) {
+        super(in);
+        
+        this.limit = limit;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (count < limit) {
+            int c = in.read();
+            if (c != -1) {
+                count++;
+            }
+            return c;
+        }
+        return -1;
+    }
+    
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (count < limit) {
+            if (limit - count < len) {
+                len = limit - count;
+            }
+            int n = in.read(b, off, len);
+            if (n > 0) {
+                count += n;
+            }
+            return n;
+        }
+        return -1;
+    }
+    
+    /**
+     * Close this input stream. Finishes reading any pending chunks until
+     * the last chunk is received. Does <b>not</b> close the underlying input
+     * stream.
+     *
+     * @see java.io.FilterInputStream#close()
+     */
+    @Override
+    public void close() throws IOException {
+        if (in == null) {
+            return;
+        }
+        try {
+            int remains = limit - count;
+            if (remains > 0) {
+                in.skip(remains);
+            }
+        } finally {
+            in = null;
+        }
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/BoundedInputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/BoundedInputStream.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/ChunkedInputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/ChunkedInputStream.java?rev=1170211&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/ChunkedInputStream.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/ChunkedInputStream.java Tue Sep 13 15:34:24 2011
@@ -0,0 +1,197 @@
+/*
+ * 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.jackrabbit.mk.util;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Input stream that reads and decodes HTTP chunks, assuming that no chunk
+ * exceeds 65535 bytes and that a chunk's length is represented by exactly 4
+ * hexadecimal characters.
+ */
+public class ChunkedInputStream extends FilterInputStream {
+
+    /**
+     * Maximum chunk size.
+     */
+    private static final int MAX_CHUNK_SIZE = 65535;
+
+    /**
+     * CR + LF combination.
+     */
+    private static final byte[] CRLF = "\r\n".getBytes();
+
+    /**
+     * Chunk prefix (length encoded as hexadecimal string).
+     */
+    private final byte[] prefix = new byte[4];
+
+    /**
+     * Chunk data.
+     */
+    private final byte[] data = new byte[MAX_CHUNK_SIZE];
+
+    /**
+     * Chunk suffix (CR + LF).
+     */
+    private final byte[] suffix = new byte[2];
+
+    /**
+     * Current offset.
+     */
+    private int offset;
+
+    /**
+     * Chunk length.
+     */
+    private int length;
+
+    /**
+     * Flag indicating whether the last chunk was read.
+     */
+    private boolean lastChunk;
+
+    /**
+     * Create a new instance of this class.
+     *
+     * @param in input stream
+     */
+    public ChunkedInputStream(InputStream in) {
+        super(in);
+    }
+
+    /* (non-Javadoc)
+     * @see java.io.FilterInputStream#read()
+     */
+    public int read() throws IOException {
+        if (!lastChunk) {
+            if (offset == length) {
+                readChunk();
+            }
+            if (offset < length) {
+                return data[offset++] & 0xff;
+            }
+        }
+        return -1;
+    }
+
+    /* (non-Javadoc)
+     * @see java.io.FilterInputStream#read(byte[], int, int)
+     */
+    public int read(byte[] b, int off, int len) throws IOException {
+        int read = 0;
+        while (read < len && !lastChunk) {
+            if (offset == length) {
+                readChunk();
+            }
+            int available = Math.min(len - read, length - offset);
+            System.arraycopy(data, offset, b, off + read, available);
+            read += available;
+            offset += available;
+        }
+        return read == 0 && lastChunk ? -1 : read;
+    }
+
+    /**
+     * Read a chunk from the underlying input stream.
+     *
+     * @throws IOException if an error occurs
+     */
+    private void readChunk() throws IOException {
+        offset = length = 0;
+
+        readFully(in, prefix);
+        length = parseInt(prefix);
+        if (length < 0 || length > MAX_CHUNK_SIZE) {
+            String msg = "Chunk size smaller than 0 or bigger than " + MAX_CHUNK_SIZE;
+            throw new IOException(msg);
+        }
+        readFully(in, suffix);
+        if (!Arrays.equals(suffix, CRLF)) {
+            String msg = "Missing carriage return/line feed combination.";
+            throw new IOException(msg);
+        }
+
+        readFully(in, data, 0, length);
+        readFully(in, suffix);
+        if (!Arrays.equals(suffix, CRLF)) {
+            String msg = "Missing carriage return/line feed combination.";
+            throw new IOException(msg);
+        }
+
+        if (length == 0) {
+            lastChunk = true;
+        }
+    }
+    
+    private static void readFully(InputStream in, byte[] b) throws IOException {
+        readFully(in, b, 0, b.length);
+    }
+    
+    private static void readFully(InputStream in, byte[] b, int off, int len) throws IOException {
+        int count = IOUtils.readFully(in, b, off, len);
+        if (count < len) {
+            String msg = String.format("Expected %d bytes, actually received: %d",
+                    len, count);
+            throw new EOFException(msg);
+        }
+    }
+
+    /**
+     * Parse an integer that is given in its hexadecimal representation as
+     * a byte array.
+     *
+     * @param b byte array containing 4 ASCII characters
+     * @return parsed integer
+     */
+    private static int parseInt(byte[] b) throws IOException {
+        int result = 0;
+
+        for (int i = 0; i < 4; i++) {
+            int c = (int) b[i];
+            result <<= 4;
+            if (c >= '0' && c <= '9') {
+                result += c - '0';
+            } else if (c >= 'A' && c <= 'F') {
+                result += c - 'A' + 10;
+            } else if (c >= 'a' && c <= 'f') {
+                result += c - 'a' + 10;
+            } else {
+                String msg = "Not a hexadecimal character: " + c;
+                throw new IOException(msg);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Close this input stream. Finishes reading any pending chunks until
+     * the last chunk is received. Does <b>not</b> close the underlying input
+     * stream.
+     *
+     * @see java.io.FilterInputStream#close()
+     */
+    public void close() throws IOException {
+        while (!lastChunk) {
+            readChunk();
+        }
+    }
+}

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/ChunkedInputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/ChunkedInputStream.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/IOUtils.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/IOUtils.java?rev=1170211&r1=1170210&r2=1170211&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/IOUtils.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/IOUtils.java Tue Sep 13 15:34:24 2011
@@ -298,5 +298,4 @@ public class IOUtils {
         }
         return count;
     }
-
 }



Mime
View raw message