/* * 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 com.sun.jini.jeri.internal.http; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; /** * Class for writing HTTP messages. Each instance writes a single HTTP * message. * * @author Sun Microsystems, Inc. * */ class MessageWriter { private static final int CHUNK_SIZE = 512; /* state values */ private static final int START = 0; private static final int HEADER = 1; private static final int CONTENT = 2; private static final int DONE = 3; private final OutputStream out; private final OutputStream cout; private int state = START; private Header header; /** * Creates new writer on top of given output stream. */ MessageWriter(OutputStream out, boolean chunked) { this.out = out; cout = chunked ? (OutputStream) new ChunkedOutputStream() : (OutputStream) new ByteArrayOutputStream(); } /** * Writes HTTP message start line. */ void writeStartLine(StartLine line) throws IOException { updateState(START, HEADER); line.write(out); } /** * "Writes" HTTP message header (the header may not actually be written * until after the message content length is known). The caller should * avoid using the passed header after invoking this method. */ void writeHeader(Header header) throws IOException { updateState(HEADER, CONTENT); if (cout instanceof ChunkedOutputStream) { header.setField("Transfer-Encoding", "chunked"); header.setField("Content-Length", null); header.write(out); } else { this.header = header; } } /** * Writes message content. */ void writeContent(byte[] b, int off, int len) throws IOException { updateState(CONTENT, CONTENT); cout.write(b, off, len); } /** * Writes message trailer (if not using chunked output, merges trailer with * header before writing), completing message output. Flushes underlying * output stream once trailer has been written. */ void writeTrailer(Header trailer) throws IOException { updateState(CONTENT, DONE); cout.close(); if (cout instanceof ChunkedOutputStream) { if (trailer != null) { trailer.write(out); } else { writeLine(out, ""); } } else { ByteArrayOutputStream bout = (ByteArrayOutputStream) cout; header.merge(trailer); header.setField("Content-Length", Integer.toString(bout.size())); header.setField("Transfer-Encoding", null); header.write(out); bout.writeTo(out); } out.flush(); } /** * Flushes written data to underlying output stream. Throws * IllegalStateException if called after message has been fully written. */ void flush() throws IOException { if (state == DONE) { throw new IllegalStateException(); } if (state == CONTENT) { cout.flush(); } out.flush(); } /** * Writes line to given output stream in ASCII, terminated by HTTP * end-of-line sequence "\r\n". */ static void writeLine(OutputStream out, String line) throws IOException { // REMIND: use Charset? line += "\r\n"; int len = line.length(); for (int i = 0; i < len; i++) { out.write(line.charAt(i)); } } private void updateState(int oldState, int newState) { if (state != oldState) { throw new IllegalStateException(); } state = newState; } /** * Output stream for writing chunked transfer-coded content. */ private class ChunkedOutputStream extends OutputStream { private final byte[] buf = new byte[CHUNK_SIZE]; private int pos = 0; ChunkedOutputStream() {} public void write(int val) throws IOException { write(new byte[]{ (byte) val }, 0, 1); } public void write(byte[] b, int off, int len) throws IOException { while (len > 0) { int avail = buf.length - pos; if (avail > 0) { int ncopy = Math.min(len, avail); System.arraycopy(b, off, buf, pos, ncopy); pos += ncopy; off += ncopy; len -= ncopy; } else { flush(); } } } public void flush() throws IOException { if (pos > 0) { writeLine(out, Integer.toString(pos, 16)); out.write(buf, 0, pos); writeLine(out, ""); pos = 0; } } public void close() throws IOException { flush(); writeLine(out, "0"); } } }