Return-Path: X-Original-To: apmail-jackrabbit-oak-commits-archive@minotaur.apache.org Delivered-To: apmail-jackrabbit-oak-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 15F38DF66 for ; Tue, 19 Jun 2012 09:40:27 +0000 (UTC) Received: (qmail 72325 invoked by uid 500); 19 Jun 2012 09:40:26 -0000 Delivered-To: apmail-jackrabbit-oak-commits-archive@jackrabbit.apache.org Received: (qmail 72286 invoked by uid 500); 19 Jun 2012 09:40:26 -0000 Mailing-List: contact oak-commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: oak-commits@jackrabbit.apache.org Delivered-To: mailing list oak-commits@jackrabbit.apache.org Received: (qmail 72253 invoked by uid 99); 19 Jun 2012 09:40:25 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 19 Jun 2012 09:40:25 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 19 Jun 2012 09:40:14 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id CDC5C2388E94; Tue, 19 Jun 2012 09:39:49 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1351618 [2/3] - in /jackrabbit/oak/trunk: ./ oak-core/ oak-it/mk/ oak-it/mk/src/main/java/org/apache/jackrabbit/mk/test/ oak-it/osgi/ oak-it/osgi/src/test/java/org/apache/jackrabbit/oak/osgi/ oak-mk-api/ oak-mk-api/src/ oak-mk-api/src/main... Date: Tue, 19 Jun 2012 09:39:45 -0000 To: oak-commits@jackrabbit.apache.org From: dpfister@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120619093949.CDC5C2388E94@eris.apache.org> Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedInputStream.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedInputStream.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedInputStream.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedInputStream.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,223 @@ +/* + * 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.remote.util; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import org.apache.jackrabbit.mk.util.IOUtils; + +/** + * Input stream that reads and decodes HTTP chunks, assuming that no chunk + * exceeds 32768 bytes and that a chunk's length is represented by exactly 4 + * hexadecimal characters. + */ +public class ChunkedInputStream extends FilterInputStream { + + /** + * Maximum chunk size. + */ + public static final int MAX_CHUNK_SIZE = 0x100000; + + /** + * CR + LF combination. + */ + private static final byte[] CRLF = "\r\n".getBytes(); + + /** + * 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; + + /** + * Flag indicating whether there was an error decomposing a chunk. + */ + private boolean chunkError; + + /** + * 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; + + length = readLength(in); + if (length < 0 || length > MAX_CHUNK_SIZE) { + chunkError = true; + String msg = "Chunk size smaller than 0 or bigger than " + MAX_CHUNK_SIZE; + throw new IOException(msg); + } + readFully(in, data, 0, length); + readFully(in, suffix); + if (!Arrays.equals(suffix, CRLF)) { + chunkError = true; + String msg = "Missing carriage return/line feed combination."; + throw new IOException(msg); + } + + if (length == 0) { + lastChunk = true; + } + } + + private int readLength(InputStream in) throws IOException { + int len = 0; + + for (int i = 0; i < 5; i++) { + int n, ch = in.read(); + if (ch == -1) { + break; + } + if (ch >= '0' && ch <= '9') { + n = (ch - '0'); + } else if (ch >= 'A' && ch <= 'F') { + n = (ch - 'A' + 10); + } else if (ch >= 'a' && ch <= 'f') { + n = (ch - 'a' + 10); + } else if (ch == '\r') { + ch = in.read(); + if (ch != '\n') { + chunkError = true; + String msg = "Missing carriage return/line feed combination."; + throw new IOException(msg); + } + return len; + } else { + chunkError = true; + String msg = String.format("Expected hexadecimal character, actual: %c", ch); + throw new IOException(msg); + } + len = len * 16 + n; + } + readFully(in, suffix); + if (!Arrays.equals(suffix, CRLF)) { + chunkError = true; + String msg = "Missing carriage return/line feed combination."; + throw new IOException(msg); + } + return len; + } + + 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); + } + } + + /** + * Recycle this input stream. + * + * @param in new underlying input stream + */ + public void recycle(InputStream in) { + this.in = in; + + offset = length = 0; + lastChunk = false; + } + + /** + * Close this input stream. Finishes reading any pending chunks until + * the last chunk is received. Does not close the underlying input + * stream. + * + * @see java.io.FilterInputStream#close() + */ + public void close() throws IOException { + if (in != null) { + try { + while (!chunkError && !lastChunk) { + readChunk(); + } + } finally { + in = null; + } + } + } +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedInputStream.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedInputStream.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedOutputStream.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedOutputStream.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedOutputStream.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedOutputStream.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,189 @@ +/* + * 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.remote.util; + +import java.io.FilterOutputStream; + +import java.io.IOException; +import java.io.OutputStream; + +import static org.apache.jackrabbit.mk.remote.util.ChunkedInputStream.MAX_CHUNK_SIZE; + +/** + * Output stream that encodes and writes HTTP chunks. + */ +public class ChunkedOutputStream extends FilterOutputStream { + + /** + * CR + LF combination. + */ + private static final byte[] CRLF = "\r\n".getBytes(); + + /** + * Last chunk. + */ + private static final byte[] LAST_CHUNK = "0000\r\n\r\n".getBytes(); + + /** + * Chunk prefix (length encoded as hexadecimal string). + */ + private final byte[] prefix = new byte[4]; + + /** + * Chunk data. + */ + private final byte[] data; + + /** + * Current offset. + */ + private int offset; + + /** + * Create a new instance of this class. + * + * @param out underlying output stream. + * @param size internal buffer size + * @throws IllegalArgumentException if {@code size} is smaller than 1 + * or bigger than {@code 65535} + */ + public ChunkedOutputStream(OutputStream out, int size) { + super(out); + + if (size < 1 || size > MAX_CHUNK_SIZE) { + String msg = "Chunk size smaller than 1 or bigger than " + MAX_CHUNK_SIZE; + throw new IllegalArgumentException(msg); + } + this.data = new byte[size]; + } + + /** + * Create a new instance of this class. + * + * @param out underlying output stream. + */ + public ChunkedOutputStream(OutputStream out) { + this(out, MAX_CHUNK_SIZE); + } + + /* (non-Javadoc) + * @see java.io.FilterOutputStream#write(int) + */ + public void write(int b) throws IOException { + if (offset == data.length) { + writeChunk(); + } + data[offset++] = (byte) (b & 0xff); + } + + /* (non-Javadoc) + * @see java.io.FilterOutputStream#write(byte[], int, int) + */ + public void write(byte[] b, int off, int len) throws IOException { + int written = 0; + while (written < len) { + if (offset == data.length) { + writeChunk(); + } + int available = Math.min(len - written, data.length - offset); + System.arraycopy(b, off + written, data, offset, available); + written += available; + offset += available; + } + } + + /** + * Writes the contents of the internal buffer as chunk to the underlying + * output stream. + * + * @throws IOException if an error occurs + */ + private void writeChunk() throws IOException { + toHexString(offset, prefix); + out.write(prefix); + out.write(CRLF); + out.write(data, 0, offset); + out.write(CRLF); + offset = 0; + } + + /** + * 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; + } + } + + /** + * Flush the contents of the internal buffer to the underlying output + * stream as a chunk if it is non-zero. Never do that for a zero-size + * chunk as this would indicate EOF. + * + * @see java.io.FilterOutputStream#flush() + */ + public void flush() throws IOException { + if (offset > 0) { + writeChunk(); + } + super.flush(); + } + + /** + * Recycle this output stream. + * + * @param out new underlying output stream + */ + public void recycle(OutputStream out) { + this.out = out; + offset = 0; + } + + /** + * Close this output stream. Flush the contents of the internal buffer + * and writes the last chunk to the underlying output stream. Sets + * the internal reference to the underlying output stream to + * {@code null}. Does not close the underlying output stream. + * + * @see java.io.FilterOutputStream#close() + */ + public void close() throws IOException { + if (out == null) { + return; + } + try { + if (offset > 0) { + writeChunk(); + } + out.write(LAST_CHUNK); + } finally { + out = null; + } + } +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedOutputStream.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/ChunkedOutputStream.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/package-info.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/package-info.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/package-info.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/package-info.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,26 @@ +/* + * 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. + */ + +@Version("0.1") +@Export(optional = "provide:=true") +package org.apache.jackrabbit.mk.remote.util; + +import aQute.bnd.annotation.Export; +import aQute.bnd.annotation.Version; + Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/package-info.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/remote/util/package-info.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,145 @@ +/* + * 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.server; + +import java.io.InputStream; +import java.io.IOException; + +/** + * Stream that reads bytes until it sees a given string boundary, preceded + * by CR+LF, as used in multipart/form-data uploads. + */ +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) { + this.in = in; + this.boundary = ("\r\n" + boundary).getBytes(); + + // Must be able to unread this many bytes + if (size < this.boundary.length + 2) { + size = this.boundary.length + 2; + } + buf = new byte[size]; + } + + @Override + public int read() throws IOException { + if (eos) { + return -1; + } + byte[] b = new byte[1]; + int count = read(b, 0, 1); + if (count == -1) { + return -1; + } + return b[0] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (eos) { + return -1; + } + if (offset == count) { + fillBuffer(); + if (eos) { + return -1; + } + } + return copy(b, off, len); + } + + private void fillBuffer() throws IOException { + if (boundaryIndex > 0) { + System.arraycopy(boundary, 0, buf, 0, boundaryIndex); + } + offset = boundaryIndex; + count = in.read(buf, offset, buf.length - offset); + + if (count < 0) { + eos = true; + } + count += offset; + } + + private int copy(byte[] b, int off, int len) throws IOException { + int i = 0, j = 0; + + while (offset + i < count && j < len) { + if (boundary[boundaryIndex] == buf[offset + i]) { + boundaryIndex++; + i++; + + if (boundaryIndex == boundary.length) { + eos = true; + break; + } + } else { + if (boundaryIndex > 0) { + i -= boundaryIndex; + if (i < 0) { + offset += i; + i = 0; + } + boundaryIndex = 0; + } + b[off + j] = buf[offset + i]; + i++; + j++; + } + } + offset += i; + return j == 0 && eos ? -1 : j; + } + + @Override + public void close() throws IOException { + in = null; + eos = true; + } +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/BoundaryInputStream.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,72 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; + +import org.apache.jackrabbit.mk.util.IOUtils; + +/** + * File servlet that will deliver static resources. + */ +class FileServlet implements Servlet { + + /** The one and only instance of this servlet. */ + public static FileServlet INSTANCE = new FileServlet(); + + /** Just one instance, no need to make constructor public */ + private FileServlet() {} + + @Override + public void service(Request request, Response response) throws IOException { + String file = request.getFile(); + if (file.endsWith("/")) { + file += "index.html"; + } + InputStream in = FileServlet.class.getResourceAsStream(file.substring(1)); + if (in != null) { + try { + 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 { + IOUtils.closeQuietly(in); + } + } else { + response.setStatusCode(404); + } + } + + /* Mime types table */ + private static final HashMap MIME_TYPES = new HashMap(); + + 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"); + } +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/FileServlet.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,119 @@ +/* + * 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.server; + +import org.apache.jackrabbit.mk.util.IOUtils; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * Process all HTTP requests on a single socket. + */ +public class HttpProcessor { + + private static final int INITIAL_SO_TIMEOUT = 10000; + + private static final int DEFAULT_SO_TIMEOUT = 30000; + + private static final int MAX_KEEP_ALIVE_REQUESTS = 100; + + private final Socket socket; + + private final Servlet servlet; + + private InputStream socketIn; + + private OutputStream socketOut; + + private final Request request = new Request(); + + private final Response response = new Response(); + + /** + * Create a new instance of this class. + * + * @param socket socket + * @param servlet servlet to invoke for incoming requests + */ + public HttpProcessor(Socket socket, Servlet servlet) { + this.socket = socket; + this.servlet = servlet; + } + + /** + * Process all requests on a single socket. + * + * @throws IOException if an I/O error occurs + */ + public void process() throws IOException { + try { + socketIn = new BufferedInputStream(socket.getInputStream()); + socketOut = new BufferedOutputStream(socket.getOutputStream()); + + socket.setSoTimeout(INITIAL_SO_TIMEOUT); + + for (int requestNum = 0;; requestNum++) { + if (!process(requestNum)) { + break; + } + if (requestNum == 0) { + socket.setSoTimeout(DEFAULT_SO_TIMEOUT); + } + } + } finally { + IOUtils.closeQuietly(socketOut); + IOUtils.closeQuietly(socketIn); + IOUtils.closeQuietly(socket); + } + } + + /** + * Process a single request. + * + * @param requestNum number of this request on the same persistent connection + * @return {@code true} if the connection should be kept alive; + * {@code false} otherwise + * + * @throws IOException if an I/O error occurs + */ + private boolean process(int requestNum) throws IOException { + try { + request.parse(socketIn); + } catch (IOException e) { + if (requestNum == 0) { + // ignore errors on the very first request (might be wrong protocol) + return false; + } + throw e; + } + try { + boolean keepAlive = request.isKeepAlive() && + (requestNum + 1 < MAX_KEEP_ALIVE_REQUESTS); + response.recycle(socketOut, keepAlive); + servlet.service(request, response); + return keepAlive; + } finally { + IOUtils.closeQuietly(request); + IOUtils.closeQuietly(response); + } + } +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/HttpProcessor.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,355 @@ +/* + * 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.server; + +import java.io.IOException; +import java.io.PrintStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +import org.apache.jackrabbit.mk.api.MicroKernel; +import org.apache.jackrabbit.mk.api.MicroKernelException; +import org.apache.jackrabbit.mk.json.JsopBuilder; +import org.apache.jackrabbit.mk.util.MicroKernelInputStream; +import org.apache.jackrabbit.mk.util.IOUtils; + +/** + * Servlet handling requests directed at a {@code MicroKernel} instance. + */ +class MicroKernelServlet { + + /** The one and only instance of this servlet. */ + public static MicroKernelServlet INSTANCE = new MicroKernelServlet(); + + /** Just one instance, no need to make constructor public */ + private MicroKernelServlet() {} + + public void service(MicroKernel mk, Request request, Response response) throws IOException { + String file = request.getFile(); + int dotIndex = file.indexOf('.'); + if (dotIndex == -1) { + dotIndex = file.length(); + } + Command command = COMMANDS.get(file.substring(1, dotIndex)); + if (command != null && mk != null) { + try { + command.execute(mk, request, response); + } catch (MicroKernelException e) { + response.setStatusCode(500); + response.setContentType("text/plain"); + e.printStackTrace(new PrintStream(response.getOutputStream())); + } catch (Throwable e) { + response.setStatusCode(500); + response.setContentType("text/plain"); + e.printStackTrace(new PrintStream(response.getOutputStream())); + } + return; + } + response.setStatusCode(404); + } + + private static interface Command { + + void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException; + } + + private static final Map COMMANDS = new HashMap(); + + static { + COMMANDS.put("getHeadRevision", new GetHeadRevision()); + COMMANDS.put("getRevisionHistory", new GetRevisionHistory()); + COMMANDS.put("waitForCommit", new WaitForCommit()); + COMMANDS.put("getJournal", new GetJournal()); + COMMANDS.put("diff", new Diff()); + COMMANDS.put("nodeExists", new NodeExists()); + COMMANDS.put("getChildNodeCount", new GetChildNodeCount()); + COMMANDS.put("getNodes", new GetNodes()); + COMMANDS.put("commit", new Commit()); + COMMANDS.put("branch", new Branch()); + COMMANDS.put("merge", new Merge()); + COMMANDS.put("getLength", new GetLength()); + COMMANDS.put("read", new Read()); + COMMANDS.put("write", new Write()); + } + + static class GetHeadRevision implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + response.setContentType("text/plain"); + response.write(mk.getHeadRevision()); + } + } + + static class GetRevisionHistory implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + long since = request.getParameter("since", 0L); + int maxEntries = request.getParameter("max_entries", 10); + String path = request.getParameter("path", ""); + + response.setContentType("application/json"); + String json = mk.getRevisionHistory(since, maxEntries, path); + if (request.getHeaders().containsKey("User-Agent")) { + json = JsopBuilder.prettyPrint(json); + } + response.write(json); + } + } + + static class WaitForCommit implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String headRevision = mk.getHeadRevision(); + + String oldHead = request.getParameter("revision_id", headRevision); + long maxWaitMillis = request.getParameter("max_wait_millis", 0L); + + String currentHead; + + try { + currentHead = mk.waitForCommit(oldHead, maxWaitMillis); + } catch (InterruptedException e) { + throw new MicroKernelException(e); + } + + response.setContentType("text/plain"); + response.write(currentHead == null ? "null" : currentHead); + } + } + + static class GetJournal implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String headRevision = mk.getHeadRevision(); + + String fromRevisionId = request.getParameter("from_revision_id", headRevision); + String toRevisionId = request.getParameter("to_revision_id", headRevision); + String path = request.getParameter("path", ""); + + response.setContentType("application/json"); + String json = mk.getJournal(fromRevisionId, toRevisionId, path); + if (request.getHeaders().containsKey("User-Agent")) { + json = JsopBuilder.prettyPrint(json); + } + response.write(json); + } + } + + static class Diff implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String headRevision = mk.getHeadRevision(); + + String fromRevisionId = request.getParameter("from_revision_id", headRevision); + String toRevisionId = request.getParameter("to_revision_id", headRevision); + String path = request.getParameter("path", ""); + + response.setContentType("application/json"); + String json = mk.diff(fromRevisionId, toRevisionId, path); + if (request.getHeaders().containsKey("User-Agent")) { + json = JsopBuilder.prettyPrint(json); + } + response.write(json); + } + } + + static class NodeExists implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String headRevision = mk.getHeadRevision(); + + String path = request.getParameter("path", "/"); + String revisionId = request.getParameter("revision_id", headRevision); + + response.setContentType("text/plain"); + response.write(Boolean.toString(mk.nodeExists(path, revisionId))); + } + } + + static class GetChildNodeCount implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String headRevision = mk.getHeadRevision(); + + String path = request.getParameter("path", "/"); + String revisionId = request.getParameter("revision_id", headRevision); + + response.setContentType("text/plain"); + response.write(Long.toString(mk.getChildNodeCount(path, revisionId))); + } + } + + static class GetNodes implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String headRevision = mk.getHeadRevision(); + + String path = request.getParameter("path", "/"); + String revisionId = request.getParameter("revision_id", headRevision); + int depth = request.getParameter("depth", 1); + long offset = request.getParameter("offset", 0L); + int maxChildNodes = request.getParameter("max_child_nodes", -1); + String filter = request.getParameter("filter", ""); + + response.setContentType("application/json"); + String json = mk.getNodes(path, revisionId, depth, offset, maxChildNodes, filter); + // OAK-48: MicroKernel.getNodes() should return null for not existing nodes instead of throwing an exception + if (json == null) { + json = "null"; + } + if (request.getHeaders().containsKey("User-Agent")) { + json = JsopBuilder.prettyPrint(json); + } + response.write(json); + } + } + + static class Commit implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String headRevision = mk.getHeadRevision(); + + String path = request.getParameter("path", "/"); + String jsonDiff = request.getParameter("json_diff"); + String revisionId = request.getParameter("revision_id", headRevision); + String message = request.getParameter("message"); + + String newRevision = mk.commit(path, jsonDiff, revisionId, message); + + response.setContentType("text/plain"); + response.write(newRevision); + } + } + + static class Branch implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String headRevision = mk.getHeadRevision(); + + String trunkRevisionId = request.getParameter("trunk_revision_id", headRevision); + + String newRevision = mk.branch(trunkRevisionId); + + response.setContentType("text/plain"); + response.write(newRevision); + } + } + + static class Merge implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String branchRevisionId = request.getParameter("branch_revision_id"); + String message = request.getParameter("message"); + + String newRevision = mk.merge(branchRevisionId, message); + + response.setContentType("text/plain"); + response.write(newRevision); + } + } + + static class GetLength implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String blobId = request.getParameter("blob_id", ""); + long length = mk.getLength(blobId); + + response.setContentType("text/plain"); + response.write(Long.toString(length)); + } + } + + static class Read implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + String blobId = request.getParameter("blob_id", ""); + 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); + 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); + } + } + } + } + + static class Write implements Command { + + @Override + public void execute(MicroKernel mk, Request request, Response response) + throws IOException, MicroKernelException { + + InputStream in = request.getFileParameter("file"); + + String blobId = mk.write(in); + + response.setContentType("text/plain"); + response.write(blobId); + } + } +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/MicroKernelServlet.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Request.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Request.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Request.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Request.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,286 @@ +/* + * 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.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.LinkedHashMap; +import java.util.Map; + +import org.apache.jackrabbit.mk.remote.util.BoundedInputStream; +import org.apache.jackrabbit.mk.remote.util.ChunkedInputStream; +import org.apache.jackrabbit.mk.util.IOUtils; + +/** + * HTTP Request implementation. + */ +class Request implements Closeable { + + private static final String HTTP_11_PROTOCOL = "HTTP/1.1"; + + private InputStream in; + + private String method; + + private String file; + + private String queryString; + + private String protocol; + + private final Map headers = new LinkedHashMap(); + + private boolean paramsChecked; + + private final Map params = new LinkedHashMap(); + + private final ChunkedInputStream chunkedIn = new ChunkedInputStream(null); + + private InputStream reqIn; + + /** + * Parse a request. This automatically resets any internal state, so it can be + * used multiple times + * + * @param in input stream + * @throws IOException if an I/O error occurs + */ + void parse(InputStream in) throws IOException { + String requestLine = readLine(in); + + String[] parts = requestLine.split(" "); + if (parts.length != 3) { + String msg = String.format("Bad HTTP request line: %s", requestLine); + throw new IOException(msg); + } + method = parts[0]; + + String uri = parts[1]; + int index = uri.lastIndexOf('?'); + if (index == -1) { + file = uri; + queryString = null; + } else { + file = uri.substring(0, index); + queryString = uri.substring(index + 1); + } + + protocol = parts[2]; + + headers.clear(); + + for (;;) { + String headerLine = readLine(in); + if (headerLine.length() == 0) { + break; + } + parts = headerLine.split(":"); + if (parts.length == 2) { + headers.put(parts[0].trim(), parts[1].trim()); + } + } + + params.clear(); + paramsChecked = false; + reqIn = null; + + this.in = in; + } + + /** + * Read a single line, terminated by a CR LF combination from an {@code InputStream}. + * + * @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); + } + } + } + + public String getMethod() { + return method; + } + + public String getFile() { + return file; + } + + private String getContentType() { + String ct = headers.get("Content-Type"); + if (ct != null) { + int sep = ct.indexOf(';'); + if (sep != -1) { + ct = ct.substring(0, sep).trim(); + } + } + return ct; + } + + private int getContentLength() { + String s = headers.get("Content-Length"); + if (s != null) { + try { + return Integer.parseInt(s); + } catch (RuntimeException e) { + /* ignore */ + } + } + return -1; + } + + public Map getHeaders() { + return headers; + } + + public String getQueryString() { + return queryString; + } + + public String getParameter(String name) throws IOException { + if (!paramsChecked) { + try { + String contentType = getContentType(); + if ("application/x-www-form-urlencoded".equals(contentType)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + IOUtils.copy(getInputStream(), out); + collectParameters(out.toString(), params); + } + } finally { + paramsChecked = true; + } + } + return params.get(name); + } + + public String getParameter(String name, String defaultValue) throws IOException { + String s = getParameter(name); + if (s != null) { + return s; + } + return defaultValue; + } + + public int getParameter(String name, int defaultValue) throws IOException { + String s = getParameter(name); + if (s != null) { + try { + return Integer.parseInt(s); + } catch (RuntimeException e) { + /* ignore */ + } + } + return defaultValue; + } + + public long getParameter(String name, long defaultValue) throws IOException { + String s = getParameter(name); + if (s != null) { + try { + return Long.parseLong(s); + } catch (RuntimeException e) { + /* ignore */ + } + } + return defaultValue; + } + + public InputStream getFileParameter(String name) throws IOException { + String ct = getContentType(); + if (ct == null || !ct.startsWith("multipart/form-data")) { + return null; + } + if (reqIn != null) { + /* might already be consumed */ + return null; + } + + InputStream body = getInputStream(); + String boundary = readLine(body); + + for (;;) { + String line = readLine(body); + if (line.length() == 0) { + break; + } + // TODO evaluate other information (such as mime type) + } + return new BoundaryInputStream(body, boundary); + } + + private static void collectParameters(String s, Map map) throws IOException { + for (String param : s.split("&")) { + String[] nv = param.split("=", 2); + if (nv.length == 2) { + map.put(URLDecoder.decode(nv[0], "UTF-8"), URLDecoder.decode(nv[1], "UTF-8")); + } + } + } + + public InputStream getInputStream() { + if (reqIn == null) { + String encoding = headers.get("Transfer-Encoding"); + if ("chunked".equalsIgnoreCase(encoding)) { + chunkedIn.recycle(in); + reqIn = chunkedIn; + } else { + int contentLength = getContentLength(); + if (contentLength == -1) { + contentLength = 0; + } + reqIn = new BoundedInputStream(in, contentLength); + } + } + return reqIn; + } + + boolean isKeepAlive() { + return HTTP_11_PROTOCOL.equals(protocol); + } + + @Override + public void close() { + if (in != null) { + try { + // Consume a possibly non-empty body by triggering the + // creation of our request input stream + getInputStream(); + IOUtils.closeQuietly(reqIn); + } finally { + in = null; + } + } + } +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Request.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Request.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Response.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Response.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Response.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Response.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,280 @@ +/* + * 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.server; + +import java.io.Closeable; +import java.io.IOException; +import java.io.OutputStream; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.jackrabbit.mk.util.IOUtils; + +import static org.apache.jackrabbit.mk.remote.util.ChunkedInputStream.MAX_CHUNK_SIZE; + +/** + * HTTP Response implementation. + */ +class Response implements Closeable { + + private OutputStream out; + + private boolean keepAlive; + + private boolean headersSent; + + private boolean committed; + + private boolean chunked; + + private int statusCode; + + private String contentType; + + private final BodyOutputStream bodyOut = new BodyOutputStream(); + + private OutputStream respOut; + + private final Map headers = new LinkedHashMap(); + + /** + * Recycle this instance, using another output stream and a keep-alive flag. + * + * @param out output stream + * @param keepAlive whether to keep alive the connection + */ + void recycle(OutputStream out, boolean keepAlive) { + this.out = out; + this.keepAlive = keepAlive; + + headersSent = committed = chunked = false; + statusCode = 0; + contentType = null; + bodyOut.reset(); + respOut = null; + headers.clear(); + } + + /** + * Return the status message associated with a status code. + * + * @param sc status code + * @return associated status message + */ + private static String getStatusMsg(int sc) { + switch (sc) { + case 200: + return "OK"; + case 400: + return "Bad request"; + case 401: + return "Unauthorized"; + case 404: + return "Not found"; + default: + return "Internal server error"; + } + } + + private void sendHeaders() throws IOException { + if (headersSent) { + return; + } + + headersSent = true; + + int statusCode = this.statusCode; + if (statusCode == 0) { + statusCode = 200; + } + String msg = getStatusMsg(statusCode); + if (respOut == null) { + /* Generate minimal body */ + String body = String.format( + "" + + "" + + "%d %s" + + "" + + "

%s

" + + "", statusCode, msg, msg); + setContentType("text/html"); + write(body); + } + + writeLine(String.format("HTTP/1.1 %d %s", statusCode, msg)); + + if (committed) { + writeLine(String.format("Content-Length: %d", bodyOut.getCount())); + } else { + chunked = true; + writeLine("Transfer-Encoding: chunked"); + } + if (contentType != null) { + writeLine(String.format("Content-Type: %s", contentType)); + } + if (!keepAlive) { + writeLine("Connection: Close"); + } + if (headers != null) { + for (Map.Entry header : headers.entrySet()) { + writeLine(String.format("%s: %s", header.getKey(), header.getValue())); + } + } + + writeLine(""); + + if (out != null) { + out.flush(); + } + } + + @Override + public void close() throws IOException { + committed = true; + + try { + sendHeaders(); + IOUtils.closeQuietly(respOut); + + if (out != null) { + out.flush(); + } + } finally { + out = null; + } + } + + private void writeLine(String s) throws IOException { + if (out == null) { + return; + } + out.write(s.getBytes()); + out.write("\r\n".getBytes()); + } + + /** + * Write some bytes to the body of the response. + * @param b buffer + * @param off offset + * @param len length + * @throws IOException if an I/O error occurs + */ + void writeBody(byte[] b, int off, int len) throws IOException { + if (out == null) { + return; + } + + sendHeaders(); + + if (chunked) { + out.write(String.format("%04X\r\n", len).getBytes()); + } + out.write(b, off, len); + if (chunked) { + out.write(("\r\n").getBytes()); + } + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public OutputStream getOutputStream() { + if (respOut == null) { + respOut = bodyOut; + } + return respOut; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public void addHeader(String name, String value) { + headers.put(name, value); + } + + public void write(String s) throws IOException { + getOutputStream().write(s.getBytes("8859_1")); + } + + /** + * Internal {@code OutputStream} passed to servlet handlers. + */ + class BodyOutputStream extends OutputStream { + + /** + * Buffer size chosen intentionally to not exceed maximum chunk + * size we'd like to transmit. + */ + private final byte[] buf = new byte[MAX_CHUNK_SIZE]; + + private int offset; + + /** + * Return the number of valid bytes in the buffer. + * + * @return number of bytes + */ + public int getCount() { + return offset; + } + + @Override + public void write(int b) throws IOException { + if (offset == buf.length) { + writeBody(buf, 0, offset); + offset = 0; + } + buf[offset++] = (byte) b; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + int count = 0; + while (count < len) { + if (offset == buf.length) { + writeBody(buf, 0, offset); + offset = 0; + } + int n = Math.min(len - count, buf.length - offset); + System.arraycopy(b, off + count, buf, offset, n); + count += n; + offset += n; + } + } + + @Override + public void flush() throws IOException { + if (offset > 0) { + writeBody(buf, 0, offset); + offset = 0; + } + } + + public void reset() { + offset = 0; + } + + @Override + public void close() throws IOException { + flush(); + + writeBody(buf, 0, 0); + } + } +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Response.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Response.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Server.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Server.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Server.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Server.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,240 @@ +/* + * 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.server; + +import java.io.EOFException; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.net.ServerSocketFactory; + +import org.apache.jackrabbit.mk.api.MicroKernel; + +/** + * Server exposing a {@code MicroKernel}. + */ +public class Server { + + /** java.net.ServerSocket's default backlog size. */ + private static final int BACKLOG = 50; + + private final ServerSocketFactory ssFactory; + + private AtomicReference mkref; + + private AtomicBoolean started = new AtomicBoolean(); + + private AtomicBoolean stopped = new AtomicBoolean(); + + private ServerSocket ss; + + private ExecutorService es; + + private int port; + + private InetAddress addr; + + /** + * Create a new instance of this class. + * + * @param mk micro kernel + */ + public Server(MicroKernel mk) { + this(mk, ServerSocketFactory.getDefault()); + this.mkref = new AtomicReference(mk); + } + + /** + * Create a new instance of this class. + * + * @param mk micro kernel + */ + public Server(MicroKernel mk, ServerSocketFactory ssFactory) { + this.mkref = new AtomicReference(mk); + this.ssFactory = ssFactory; + } + + /** + * 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; + } + + /** + * Set bind address. + */ + public void setBindAddress(InetAddress addr) throws IllegalStateException { + if (started.get()) { + throw new IllegalStateException("Server already started."); + } + this.addr = addr; + } + + /** + * Start this server. + * + * @throws IOException if an I/O error occurs + */ + public void start() throws IOException { + if (!started.compareAndSet(false, true)) { + return; + } + ss = createServerSocket(); + es = createExecutorService(); + + new Thread(new Runnable() { + @Override + public void run() { + accept(); + } + }, "Acceptor").start(); + } + + void accept() { + try { + while (!stopped.get()) { + final Socket socket = ss.accept(); + es.execute(new Runnable() { + @Override + public void run() { + process(socket); + } + }); + } + } catch (IOException e) { + /* ignore */ + } + } + + private ServerSocket createServerSocket() throws IOException { + return ssFactory.createServerSocket(port, BACKLOG, addr); + } + + private ExecutorService createExecutorService() { + return Executors.newCachedThreadPool(); + } + + /** + * Process a connection attempt by a client. + * + * @param socket client socket + */ + void process(Socket socket) { + try { + socket.setTcpNoDelay(true); + } catch (IOException e) { + /* ignore */ + } + + HttpProcessor processor = new HttpProcessor(socket, new Servlet() { + @Override + public void service(Request request, Response response) + throws IOException { + Server.this.service(request, response); + } + }); + + try { + processor.process(); + } catch (SocketTimeoutException e) { + /* ignore */ + } catch (EOFException e) { + /* ignore */ + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Service a request. + * + * @param request request + * @param response response + * @throws IOException if an I/O error occurs + */ + void service(Request request, Response response) throws IOException { + if (request.getMethod().equals("POST")) { + MicroKernelServlet.INSTANCE.service(mkref.get(), request, response); + } else { + FileServlet.INSTANCE.service(request, response); + } + } + + /** + * Return the server's local socket address. + * + * @return socket address or {@code null} if the server is not started + */ + public InetSocketAddress getAddress() { + if (!started.get() || stopped.get()) { + return null; + } + SocketAddress address = ss.getLocalSocketAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress isa = (InetSocketAddress) address; + if (isa.getAddress().isAnyLocalAddress()) { + try { + return new InetSocketAddress( + InetAddress.getByName("localhost"), + ss.getLocalPort()); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } else { + return isa; + } + } + return null; + } + + /** + * Stop this server. + */ + public void stop() { + if (!stopped.compareAndSet(false, true)) { + return; + } + if (es != null) { + es.shutdown(); + } + if (ss != null) { + try { + ss.close(); + } catch (IOException e) { + /* ignore */ + } + } + } + +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Server.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Server.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Servlet.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Servlet.java?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Servlet.java (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Servlet.java Tue Jun 19 09:39:41 2012 @@ -0,0 +1,27 @@ +/* + * 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.server; + +import java.io.IOException; + +/** + * Servlet interface handling HTTP requests. + */ +interface Servlet { + + public void service(Request request, Response response) throws IOException; +} Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Servlet.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/java/org/apache/jackrabbit/mk/server/Servlet.java ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/bg-body.png URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/bg-body.png?rev=1351618&view=auto ============================================================================== Binary file - no diff available. Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/bg-body.png ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/branch.html URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/branch.html?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/branch.html (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/branch.html Tue Jun 19 09:39:41 2012 @@ -0,0 +1,43 @@ + + + +µKernel prototype: branch + + + + + + +
+

Write Operations: branch

+
+ + + + + +
Trunk Revision ID
  +
+
+

+ +

+ +
+ + Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/branch.html ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/commit.html URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/commit.html?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/commit.html (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/commit.html Tue Jun 19 09:39:41 2012 @@ -0,0 +1,57 @@ + + + +µKernel prototype: commit + + + + + + +
+

Write Operations: commit

+
+ + + + + + + + + + + + + + + + + +
Path
JSON Diff +
Revision ID
Message
  +
+
+

+ +

+ +
+ + Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/commit.html ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/diff.html URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/diff.html?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/diff.html (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/diff.html Tue Jun 19 09:39:41 2012 @@ -0,0 +1,51 @@ + + + +µKernel prototype: diff + + + + + + +
+

Revision Operations: diff

+
+ + + + + + + + + + + + + +
From Revision ID
To Revision ID
Filter
  +
+
+

+ +

+ +
+ + Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/diff.html ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/footer.js URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/footer.js?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/footer.js (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/footer.js Tue Jun 19 09:39:41 2012 @@ -0,0 +1,17 @@ +/* + * 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. + */ +document.write(''); Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/footer.js ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/footer.js ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Rev Url Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getChildNodeCount.html URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getChildNodeCount.html?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getChildNodeCount.html (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getChildNodeCount.html Tue Jun 19 09:39:41 2012 @@ -0,0 +1,47 @@ + + + +µKernel prototype: getLength + + + + + + +
+

Read Operations: getChildNodeCount

+
+ + + + + + + + + +
Path
Revision ID
  +
+
+

+ +

+ +
+ + Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getChildNodeCount.html ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getHeadRevision.html URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getHeadRevision.html?rev=1351618&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getHeadRevision.html (added) +++ jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getHeadRevision.html Tue Jun 19 09:39:41 2012 @@ -0,0 +1,39 @@ + + + +µKernel prototype: getHeadRevision + + + + + + +
+

Revision Operations: getHeadRevision

+
+ +
  +
+
+

+ +

+ +
+ + Propchange: jackrabbit/oak/trunk/oak-mk-remote/src/main/resources/org/apache/jackrabbit/mk/server/getHeadRevision.html ------------------------------------------------------------------------------ svn:eol-style = native