All Downloads are FREE. Search and download functionalities are using the official Maven repository.

libcore.net.spdy.SpdyConnection Maven / Gradle / Ivy

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed 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 libcore.net.spdy;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * A socket connection to a remote peer. A connection hosts streams which can
 * send and receive data.
 */
public final class SpdyConnection implements Closeable {

    /*
     * Socket writes are guarded by this. Socket reads are unguarded but are
     * only made by the reader thread.
     */

    static final int FLAG_FIN = 0x01;
    static final int FLAG_UNIDIRECTIONAL = 0x02;

    static final int TYPE_EOF = -1;
    static final int TYPE_DATA = 0x00;
    static final int TYPE_SYN_STREAM = 0x01;
    static final int TYPE_SYN_REPLY = 0x02;
    static final int TYPE_RST_STREAM = 0x03;
    static final int TYPE_SETTINGS = 0x04;
    static final int TYPE_NOOP = 0x05;
    static final int TYPE_PING = 0x06;
    static final int TYPE_GOAWAY = 0x07;
    static final int TYPE_HEADERS = 0x08;
    static final int VERSION = 2;

    /** Guarded by this */
    private int nextStreamId;
    private final SpdyReader spdyReader;
    private final SpdyWriter spdyWriter;
    private final Executor executor;

    /**
     * User code to run in response to an incoming stream. This must not be run
     * on the read thread, otherwise a deadlock is possible.
     */
    private final IncomingStreamHandler handler;

    private final Map streams = Collections.synchronizedMap(
            new HashMap());

    private SpdyConnection(Builder builder) {
        nextStreamId = builder.client ? 1 : 2;
        spdyReader = new SpdyReader(builder.in);
        spdyWriter = new SpdyWriter(builder.out);
        handler = builder.handler;

        String name = isClient() ? "ClientReader" : "ServerReader";
        executor = builder.executor != null
                ? builder.executor
                : Executors.newCachedThreadPool(Threads.newThreadFactory(name));
        executor.execute(new Reader());
    }

    /**
     * Returns true if this peer initiated the connection.
     */
    public boolean isClient() {
        return nextStreamId % 2 == 1;
    }

    private SpdyStream getStream(int id) {
        SpdyStream stream = streams.get(id);
        if (stream == null) {
            throw new UnsupportedOperationException("TODO " + id + "; " + streams); // TODO: rst stream
        }
        return stream;
    }

    void removeStream(int streamId) {
        streams.remove(streamId);
    }

    /**
     * Returns a new locally-initiated stream.
     *
     * @param out true to create an output stream that we can use to send data
     *     to the remote peer. Corresponds to {@code FLAG_FIN}.
     * @param in true to create an input stream that the remote peer can use to
     *     send data to us. Corresponds to {@code FLAG_UNIDIRECTIONAL}.
     */
    public synchronized SpdyStream newStream(List requestHeaders, boolean out, boolean in)
            throws IOException {
        int streamId = nextStreamId; // TODO
        nextStreamId += 2;
        int flags = (out ? 0 : FLAG_FIN) | (in ? 0 : FLAG_UNIDIRECTIONAL);
        int associatedStreamId = 0;  // TODO
        int priority = 0; // TODO

        SpdyStream result = new SpdyStream(streamId, this, requestHeaders, flags);
        streams.put(streamId, result);

        spdyWriter.flags = flags;
        spdyWriter.streamId = streamId;
        spdyWriter.associatedStreamId = associatedStreamId;
        spdyWriter.priority = priority;
        spdyWriter.nameValueBlock = requestHeaders;
        spdyWriter.synStream();

        return result;
    }

    synchronized void writeSynReply(int streamId, List alternating) throws IOException {
        int flags = 0; // TODO
        spdyWriter.flags = flags;
        spdyWriter.streamId = streamId;
        spdyWriter.nameValueBlock = alternating;
        spdyWriter.synReply();
    }

    /** Writes a complete data frame. */
    synchronized void writeFrame(byte[] bytes, int offset, int length) throws IOException {
        spdyWriter.out.write(bytes, offset, length);
    }

    void writeSynResetLater(final int streamId, final int statusCode) {
        executor.execute(new Runnable() {
            @Override public void run() {
                try {
                    writeSynReset(streamId, statusCode);
                } catch (IOException ignored) {
                }
            }
        });
    }

    synchronized void writeSynReset(int streamId, int statusCode) throws IOException {
        int flags = 0; // TODO
        spdyWriter.flags = flags;
        spdyWriter.streamId = streamId;
        spdyWriter.statusCode = statusCode;
        spdyWriter.synReset();
    }

    public synchronized void flush() throws IOException {
        spdyWriter.out.flush();
    }

    @Override public synchronized void close() throws IOException {
        // TODO: graceful close; send RST frames
        // TODO: close all streams to release waiting readers
        if (executor instanceof ExecutorService) {
            ((ExecutorService) executor).shutdown();
        }
    }

    public static class Builder {
        private InputStream in;
        private OutputStream out;
        private IncomingStreamHandler handler = IncomingStreamHandler.REFUSE_INCOMING_STREAMS;
        private Executor executor;
        public boolean client;

        /**
         * @param client true if this peer initiated the connection; false if
         *     this peer accepted the connection.
         */
        public Builder(boolean client, Socket socket) throws IOException {
            this(client, socket.getInputStream(), socket.getOutputStream());
        }

        /**
         * @param client true if this peer initiated the connection; false if this
         *     peer accepted the connection.
         */
        public Builder(boolean client, InputStream in, OutputStream out) {
            this.client = client;
            this.in = in;
            this.out = out;
        }

        public Builder executor(Executor executor) {
            this.executor = executor;
            return this;
        }

        public Builder handler(IncomingStreamHandler handler) {
            this.handler = handler;
            return this;
        }

        public SpdyConnection build() {
            return new SpdyConnection(this);
        }
    }

    private class Reader implements Runnable {
        @Override public void run() {
            try {
                while (readFrame()) {
                }
                close();
            } catch (Throwable e) {
                e.printStackTrace(); // TODO
            }
        }

        private boolean readFrame() throws IOException {
            switch (spdyReader.nextFrame()) {
            case TYPE_EOF:
                return false;

            case TYPE_DATA:
                getStream(spdyReader.streamId)
                        .receiveData(spdyReader.in, spdyReader.flags, spdyReader.length);
                return true;

            case TYPE_SYN_STREAM:
                final SpdyStream stream = new SpdyStream(spdyReader.streamId, SpdyConnection.this,
                        spdyReader.nameValueBlock, spdyReader.flags);
                SpdyStream previous = streams.put(spdyReader.streamId, stream);
                if (previous != null) {
                    previous.close(SpdyStream.RST_PROTOCOL_ERROR);
                }
                executor.execute(new Runnable() {
                    @Override public void run() {
                        try {
                            handler.receive(stream);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                });
                return true;

            case TYPE_SYN_REPLY:
                // TODO: honor flags
                getStream(spdyReader.streamId).receiveReply(spdyReader.nameValueBlock);
                return true;

            case TYPE_RST_STREAM:
                getStream(spdyReader.streamId).receiveRstStream(spdyReader.statusCode);
                return true;

            case SpdyConnection.TYPE_SETTINGS:
                // TODO: implement
                System.out.println("Unimplemented TYPE_SETTINGS frame discarded");
                return true;

            case SpdyConnection.TYPE_NOOP:
            case SpdyConnection.TYPE_PING:
            case SpdyConnection.TYPE_GOAWAY:
            case SpdyConnection.TYPE_HEADERS:
                throw new UnsupportedOperationException();
            }

            // TODO: throw IOException here?
            return false;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy