com.hqme.cm.cache.StreamingServer.java Source code

Java tutorial

Introduction

Here is the source code for com.hqme.cm.cache.StreamingServer.java

Source

/** 
* This reference code is an implementation of the IEEE P2200 standard.  It is not
* a contribution to the IEEE P2200 standard.
* 
* Copyright (c) 2011 SanDisk Corporation.  All rights reserved.
* 
* 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 com.hqme.cm.cache;

import android.os.RemoteException;

import com.hqme.cm.VSDProperties;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URL;
import java.net.URLConnection;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.HTTP;

public class StreamingServer implements Runnable {
    private static final String sTag = "StreamingServer";

    private static final String tag_Close = "close";
    private static final String tag_Connection = "connection";
    private static final String tag_Get = "get";
    private static final String tag_Head = "head";
    private static final String tag_Host = "host";
    private static final String tag_Post = "post";
    private static final String tag_Query = "?";
    private static final String tag_Range = "range";
    private static final String tag_TokenKey = "token=";

    private static final String tag_PlaybackPortNumber = "playback.port";

    private static final int BUFFER_SIZE = 32768;
    private static final int MAX_CLIENTS = 8;
    private static ClientBox clientBox[] = null;

    private static ServerSocket serverSocket = null;
    private static int serverPortNumber = 0;
    private static File serverPortPrefs = null;

    private static boolean isStopping = false;

    //==================================================================================================================================
    /* HTTP */
    public enum HTTP_REQUEST_TYPE {
        RT_UNKNOWN("UNKNOWN"), RT_GET("GET"), RT_POST("POST"), RT_HEAD("HEAD"), RT_CLOSE("CLOSE");

        private String requestType;

        private HTTP_REQUEST_TYPE(String requestType) {
            this.requestType = requestType;
        }

        public String getRequestType() {
            return this.requestType;
        }

        public String toString() {
            return String.format("%s: \"%s\"", getClass().getName(), this.requestType);
        }

        public static HTTP_REQUEST_TYPE getRequestType(String httpHeaderLine) {
            String header = httpHeaderLine == null ? "" : httpHeaderLine.toLowerCase();

            if (header.startsWith(tag_Get))
                return RT_GET;

            if (header.startsWith(tag_Head))
                return RT_HEAD;

            if (header.startsWith(tag_Post))
                return RT_POST;

            if (header.startsWith(tag_Close))
                return RT_CLOSE;

            return RT_UNKNOWN;
        }
    }

    //----------------------------------------------------------------------------------------------------------------------------------
    public enum HTTP_RESPONSE_TYPE {
        RT_100_CONTINUE(100, "Continue"),

        RT_200_OK(200, "OK"), RT_201_CREATED(201, "Created"), RT_202_ACCEPTED(202,
                "Accepted"), RT_203_NON_AUTHORITATIVE_INFORMATION(203,
                        "Non-Authoritative Information"), RT_204_NO_CONTENT(204,
                                "No Content"), RT_205_RESET_CONTENT(205,
                                        "Reset Content"), RT_206_PARTIAL_CONTENT(206, "Partial Content"),

        RT_300_MULTIPLE_CHOICES(300, "Multiple Choices"), RT_301_MOVED_PERMANENTLY(301,
                "Moved Permanently"), RT_302_FOUND(302, "Found"), RT_303_SEE_OTHER(303,
                        "See Other"), RT_304_NOT_MODIFIED(304, "Not Modified"), RT_305_USE_PROXY(305,
                                "Use Proxy"), RT_307_TEMPORARY_REDIRECT(307, "Temporary Redirect"),

        RT_400_BAD_REQUEST(400, "Bad Request"), RT_401_UNAUTHORIZED(401, "Unauthorized"), RT_402_PAYMENT_REQUIRED(
                402, "Payment Required"), RT_403_FORBIDDEN(403, "Forbidden"), RT_404_NOT_FOUND(404,
                        "Not Found"), RT_405_METHOD_NOT_ALLOWED(405, "Method Not Allowed"), RT_406_NOT_ACCEPTABLE(
                                406, "Not Acceptable"), RT_407_PROXY_AUTHENTICATION_REQUIRED(407,
                                        "Proxy Authentication Required"), RT_408_REQUEST_TIMEOUT(408,
                                                "Request Timeout"), RT_409_CONFLICT(409, "Conflict"), RT_410_GONE(
                                                        410, "Gone"), RT_411_LENGTH_REQUIRED(411,
                                                                "Length Required"), RT_412_PRECONDITION_FAILED(412,
                                                                        "Precondition Failed"), RT_413_REQUEST_ENTITY_TOO_LARGE(
                                                                                413,
                                                                                "Request Entity Too Large"), RT_414_REQUEST_URI_TOO_LONG(
                                                                                        414,
                                                                                        "Request-URI Too Long"), RT_415_UNSUPPORTED_MEDIA_TYPE(
                                                                                                415,
                                                                                                "Unsupported Media Type"), RT_416_REQUESTED_RANGE_NOT_SATISFIABLE(
                                                                                                        416,
                                                                                                        "Requested Range Not Satisfiable"), RT_417_EXPECTATION_FAILED(
                                                                                                                417,
                                                                                                                "Expectation Failed"),

        RT_500_INTERNAL_SERVER_ERROR(500, "Internal Server Error"), RT_501_NOT_IMPLEMENTED(501,
                "Not Implemented"), RT_502_BAD_GATEWAY(502, "Bad Gateway"), RT_503_SERVICE_UNAVAILABLE(503,
                        "Service Unavailable"), RT_504_GATEWAY_TIMEOUT(504,
                                "Gateway Timeout"), RT_505_HTTP_VERSION_NOT_SUPPORTED(505,
                                        "HTTP Version Not Supported");

        private int responseCode;
        private String responseStatus;

        private HTTP_RESPONSE_TYPE(int responseCode, String responseStatus) {
            this.responseCode = responseCode;
            this.responseStatus = responseStatus;
        }

        public int getResponseCode() {
            return this.responseCode;
        }

        public String getResponseStatus() {
            return this.responseStatus;
        }

        public String toString() {
            return String.format("%s: %d \"%s\"", getClass().getName(), this.responseCode, this.responseStatus);
        }
    }

    // ----------------------------------------------------------------------------------------------------------------------------------
    public static int getServerPortNumber() {
        return serverPortNumber;
    }

    // ----------------------------------------------------------------------------------------------------------------------------------
    /* Thread control */
    public void stopServer() {
        UntenCacheService.debugLog(sTag, "stopServer");
        isStopping = true;
        try {
            URL term = new URL("http://localhost:" + serverPortNumber + "/");
            URLConnection conn = term.openConnection();
            conn.setDoOutput(true);
            OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
            out.write("GET /favicon.ico HTTP/1.1");
            out.close();
        } catch (Throwable fault) {
            // UntenCacheService.debugLog(sTag, "stopServer", fault);
        }
    }

    // ==================================================================================================================================
    // ==================================================================================================================================
    /* Implements Interface */
    public void run() {
        if (UntenCacheService.sIsDebugMode) {
            Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
            Thread.currentThread().setName(getClass().getName());
        }

        isStopping = false;
        serverSocket = null;
        serverPortPrefs = new File(UntenCacheService.sPluginContext.getFilesDir(), tag_PlaybackPortNumber);
        try {
            String text = new BufferedReader(new InputStreamReader(new FileInputStream(serverPortPrefs), "UTF16"),
                    1 << 10).readLine();
            serverPortNumber = Integer.valueOf(text.trim());
        } catch (Throwable ignore) {
            serverPortNumber = 0;
        }

        int retries = 2;
        while (retries-- > 0) {
            try {
                serverSocket = new ServerSocket(serverPortNumber);
                if (serverPortNumber == 0) {
                    serverPortNumber = serverSocket.getLocalPort();
                    try {
                        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(serverPortPrefs),
                                "UTF16");
                        writer.write(serverPortNumber + "\r\n");
                        writer.flush();
                        writer.close();
                    } catch (Throwable ignore) {
                    }
                }
                clientBox = new ClientBox[MAX_CLIENTS];
                retries = 0;

                UntenCacheService.debugLog(sTag, "run: Streaming Media Server is now active on TCP port # %d",
                        serverPortNumber);
                handleRequests();
            } catch (IOException fault) {
                fault.printStackTrace();
                try {
                    serverPortPrefs.delete();
                } catch (Throwable ignore) {
                }
            }
        }
    }

    // ----------------------------------------------------------------------------------------------------------------------------------
    private void handleRequests() {
        // android.os.Debug.waitForDebugger();

        try {
            Socket client = null;
            while (!isStopping)
                try {
                    client = null;
                    client = serverSocket.accept();

                    // prevent connections from remote internet addresses; clients must playback from localhost sockets only
                    SocketAddress socketAddress = client.getRemoteSocketAddress();
                    if (InetSocketAddress.class.isInstance(socketAddress)) {
                        String hostName = ((InetSocketAddress) socketAddress).getHostName();
                        if (!"localhost".equalsIgnoreCase(hostName)) {
                            // UntenCacheService.debugLog(sTag, "handleRequests: client.getRemoteSocketAddress().getHostName(): %s", hostName);
                            client.close();
                            client = null;
                            continue;
                        }
                    }
                } catch (IOException fault) {
                    isStopping = true;
                } finally {
                    synchronized (clientBox) {
                        if (isStopping) {
                            for (int i = 0; i < clientBox.length; i++) {
                                if (clientBox[i] != null && clientBox[i].thread != null
                                        && clientBox[i].thread.isAlive()) {
                                    UntenCacheService.debugLog(sTag, "run: Stopping client # %d", i);
                                    clientBox[i].handler.isStopRequested = true;
                                }
                            }
                        } else if (client != null) {
                            int i = 0;
                            for (i = 0; i < clientBox.length; i++) {
                                if (clientBox[i] == null || !clientBox[i].thread.isAlive()) {
                                    clientBox[i] = new ClientBox();
                                    clientBox[i].handler = new ClientHandler(client, i);
                                    clientBox[i].thread = new Thread(clientBox[i].handler);
                                    clientBox[i].thread.setDaemon(true);
                                    clientBox[i].thread.start();
                                    // UntenCacheService.debugLog(sTag, "handleRequests", "Accepted client # %d", i);
                                    break;
                                }
                            }
                            if (i == clientBox.length) { // End with no slot for it.
                                client.close();
                            }
                        }
                    }
                }
        } catch (Exception fault) {
            fault.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                    serverSocket = null;
                } catch (Throwable ignore) {
                }
            }
            clientBox = null;
        }
    }

    // ==================================================================================================================================
    // ==================================================================================================================================
    /* Structure to hold the threaded client */
    public class ClientBox {
        Thread thread = null;
        ClientHandler handler = null;
    }

    // ----------------------------------------------------------------------------------------------------------------------------------
    /* Structure to hold parsed request */
    public class RequestBox {
        public StringBuilder requestBody = null;
        public String requestPath = "";
        public String requestParam = "";
        public String requestRange = "";

        public String responseMIME = "";
        public String responseProtocol = "";

        public boolean isHttp10() {
            return responseProtocol.endsWith("/1.0");
        }

        public Throwable fault = null; // null = no error

        public HTTP_REQUEST_TYPE requestType = HTTP_REQUEST_TYPE.RT_UNKNOWN;
        public HTTP_RESPONSE_TYPE responseType = HTTP_RESPONSE_TYPE.RT_200_OK;

        public String toString() {
            return String.format("%s: Protocol = %s : isHttp10 = %s: Param = %s : %s : Mime = %s : Path = %s ",
                    getClass().getName(), responseProtocol, String.valueOf(isHttp10()), requestParam, requestRange,
                    responseMIME, requestPath);
        }
    }

    // ----------------------------------------------------------------------------------------------------------------------------------
    public class ClientHandler implements Runnable {
        // ============================================================
        private int clientBoxIndex = -1;
        private Socket soClient = null;

        private OutputStream outStream = null;
        private InputStream inStream = null;

        private InputStreamReader inStreamReader = null;
        private BufferedReader inBufferedReader = null;

        public RequestBox request = null;

        private boolean isCloseRequested = false;
        private boolean isStopRequested = false;

        // ============================================================
        public ClientHandler(Socket client, int index) {
            try {
                clientBoxIndex = index;
                soClient = client;
                inStream = client.getInputStream();
                outStream = client.getOutputStream();
            } catch (IOException fault) {
                fault.printStackTrace();
            }
        }

        // ------------------------------------------------------------
        public void ProcessRequest() {
            @SuppressWarnings("unused")
            String host = "";
            try {
                request = new RequestBox();

                inStreamReader = new InputStreamReader(inStream);
                inBufferedReader = new BufferedReader(inStreamReader, 1024);
                String line = inBufferedReader.readLine();
                if (line == null)
                    throw new SocketException();

                // save first line of request for further processing
                request.requestPath = line;

                // keep everything else in request.requestBody (not really necessarily but handy for debugging)
                // request.requestBody = new StringBuilder();
                while (line != null && line.length() > 0) {
                    UntenCacheService.debugLog(sTag, "ProcessRequest:  %s", line); // dump all HTTP Request...

                    String header = line.trim().toLowerCase();

                    if (header.startsWith(tag_Connection)) {
                        isCloseRequested = header.endsWith(tag_Close);
                    } else if (header.startsWith(tag_Host)) {
                        host = line.substring(tag_Host.length() + 1).trim();
                    } else if (header.startsWith(tag_Range)) {
                        request.requestRange = line;
                        request.responseType = HTTP_RESPONSE_TYPE.RT_206_PARTIAL_CONTENT;
                    }

                    // request.requestBody.append(line).append('\n');
                    line = inBufferedReader.readLine();
                }
            } catch (SocketException fault) {
                request.responseType = HTTP_RESPONSE_TYPE.RT_500_INTERNAL_SERVER_ERROR;
                request.fault = fault;
                // avoid printing debug log info for this fault because client media players commonly close their socket connections "abruptly"
                // request.fault.printStackTrace();
                return;
            } catch (IOException fault) {
                request.responseType = HTTP_RESPONSE_TYPE.RT_500_INTERNAL_SERVER_ERROR;
                request.fault = fault;
                request.fault.printStackTrace();
                return;
            } catch (Exception fault) {
                request.responseType = HTTP_RESPONSE_TYPE.RT_500_INTERNAL_SERVER_ERROR;
                request.fault = fault;
                request.fault.printStackTrace();
                return;
            } catch (Throwable fault) {
                request.responseType = HTTP_RESPONSE_TYPE.RT_500_INTERNAL_SERVER_ERROR;
                request.fault = fault;
                request.fault.printStackTrace();
                return;
            }

            request.requestType = HTTP_REQUEST_TYPE.getRequestType(request.requestPath);

            if (request.requestType == HTTP_REQUEST_TYPE.RT_GET || request.requestType == HTTP_REQUEST_TYPE.RT_POST
                    || request.requestType == HTTP_REQUEST_TYPE.RT_HEAD) {
                // get the http protocol version (e.g. HTTP/1.0 or HTTP/1.1 or later version) and extract the path string between the command and protocol info
                int tail = request.requestPath.indexOf(" HTTP/");
                if (tail >= 0) {
                    request.responseProtocol = request.requestPath.substring(tail + 1).trim();
                    request.requestPath = request.requestPath
                            .substring(request.requestType.getRequestType().length(), tail).trim();
                } else {
                    request.responseType = HTTP_RESPONSE_TYPE.RT_505_HTTP_VERSION_NOT_SUPPORTED; // unsupported protocol or garbage in request line
                }
            } else {
                request.responseType = HTTP_RESPONSE_TYPE.RT_501_NOT_IMPLEMENTED; // unsupported http request method
            }

            // extract the Uri query string (if present) from the path string
            int queryPos = request.requestPath.indexOf(tag_Query);
            if (queryPos >= 0) {
                int paramPos = queryPos + tag_Query.length();
                request.requestParam = paramPos < request.requestPath.length()
                        ? request.requestPath.substring(paramPos).trim()
                        : "";
                request.requestPath = request.requestPath.substring(0, queryPos).trim();
            }
        }

        // ------------------------------------------------------------
        boolean isDisconnected = false;

        public void ProcessResponse() {
            if (request == null || request.fault != null || isStopRequested)
                return;

            // return 403 Forbidden whenever the playback token is null, e.g. because it was omitted from the Uri, is invalid, has expired, or was requested too many times.
            // return 404 Not Found whenever the file previously used to construct the Uri no longer exists at that location.
            //
            int keyPos = request.requestParam.indexOf(tag_TokenKey);
            PlaybackTokens.PlaybackToken token = keyPos < 0 ? null
                    : PlaybackTokens
                            .getPlaybackToken(request.requestParam.substring(keyPos + tag_TokenKey.length()));
            if (token == null)
                request.responseType = HTTP_RESPONSE_TYPE.RT_403_FORBIDDEN;
            else
                try {
                    if (token.object.size() <= 0)
                        request.responseType = HTTP_RESPONSE_TYPE.RT_404_NOT_FOUND;
                    else
                        request.responseMIME = token.object.getProperty(VSDProperties.SProperty.S_TYPE.name());
                } catch (RemoteException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            if (request.responseType == HTTP_RESPONSE_TYPE.RT_200_OK
                    || request.responseType == HTTP_RESPONSE_TYPE.RT_206_PARTIAL_CONTENT) {
                synchronized (token) {
                    try {
                        UntenCacheService.debugLog(sTag, "ProcessResponse: origin = %s, name = %s, MIME = %s",
                                token.object.getProperty(VSDProperties.SProperty.S_ORIGIN.name()),
                                token.object.mName, request.responseMIME);

                        token.object.open("r", false);
                        long inFileLen = token.object.size();
                        long inRealFileLen = inFileLen;
                        long head = 0, tail = inFileLen;
                        long remain = 0;
                        int block = 0;
                        byte[] buf = new byte[BUFFER_SIZE + 16];

                        // process byte range request
                        if (request.requestRange.length() > 0) {
                            String[] range = request.requestRange.split("[\\x3d\\x2d]"); // e.g.: "Range: bytes=0-123" splits as range[0] = "Range: bytes" : range[1] = "0" : range[2] = "123"
                            try {
                                head = Integer.valueOf(range[1]);
                            } catch (Throwable fault) {
                                head = 0;
                            }
                            try {
                                tail = Integer.valueOf(range[2]);
                            } catch (Throwable fault) {
                                tail = inRealFileLen;
                            }
                            inFileLen = tail - head + 1;
                        }

                        // check if protocol is HTTP/1.0
                        String protocol = request.responseProtocol.substring(0, 4);

                        HttpResponse response = new BasicHttpResponse(new ProtocolVersion(protocol, 1, 1),
                                request.responseType.getResponseCode(), request.responseType.getResponseStatus());
                        response.addHeader(new BasicHeader(HTTP.CONTENT_LEN, Long.toString(inFileLen)));
                        response.addHeader(new BasicHeader(HTTP.CONTENT_TYPE, request.responseMIME));

                        if (request.requestRange.length() > 0) {
                            response.addHeader(new BasicHeader("Content-Range",
                                    "bytes " + head + "-" + tail + "/" + inRealFileLen));
                            response.addHeader(new BasicHeader("Accept-Ranges", "bytes"));
                        }

                        if (isCloseRequested || request.isHttp10()) {
                            isCloseRequested = true;
                            response.addHeader(new BasicHeader(HTTP.CONN_DIRECTIVE, "Close"));
                        }

                        // send reply
                        outStream.write(response.getStatusLine().toString().getBytes());
                        outStream.write("\r\n".getBytes());

                        Header[] headers = response.getAllHeaders();
                        for (int i = 0; i < headers.length; i++) {
                            outStream.write(headers[i].toString().getBytes());
                            outStream.write("\r\n".getBytes());
                            // UntenCacheService.debugLog(sTag, "ProcessResponse", "[Header] " + headers[i]);
                        }
                        outStream.write("\r\n".getBytes());
                        outStream.flush();

                        if (request.requestType == HTTP_REQUEST_TYPE.RT_HEAD)
                            return;

                        remain = inFileLen;
                        block = BUFFER_SIZE;

                        token.object.seek(head, 0);

                        while (remain > 0) {
                            block = remain > BUFFER_SIZE ? BUFFER_SIZE : (int) remain;

                            if (isStopRequested) {
                                UntenCacheService.debugLog(sTag, "ProcessResponse", "Stop requested!");
                                break;
                            }

                            block = token.object.read(buf, (int) block);
                            if (block < 0) {
                                isCloseRequested = true;
                                long error = 0 - block;
                                UntenCacheService.debugLog(sTag, "ProcessResponse @ token.object.read",
                                        "Error = %d (0x%08x)", error, error);
                                break;
                            } else {
                                outStream.write(buf, 0, block);
                            }

                            remain -= block;
                        }
                        outStream.flush();
                        return;
                    } catch (FileNotFoundException fault) {
                        request.responseType = HTTP_RESPONSE_TYPE.RT_404_NOT_FOUND;
                        request.fault = fault;
                        request.fault.printStackTrace();
                    } catch (SocketException fault) {
                        isDisconnected = true;
                        request.responseType = HTTP_RESPONSE_TYPE.RT_500_INTERNAL_SERVER_ERROR;
                        request.fault = fault;
                        // avoid printing debug log info for this fault because client media players commonly close their socket connections "abruptly"
                        // request.fault.printStackTrace();
                    } catch (IOException fault) {
                        isDisconnected = true;
                        request.responseType = HTTP_RESPONSE_TYPE.RT_500_INTERNAL_SERVER_ERROR;
                        request.fault = fault;
                        request.fault.printStackTrace();
                    } catch (Exception fault) {
                        request.responseType = HTTP_RESPONSE_TYPE.RT_500_INTERNAL_SERVER_ERROR;
                        request.fault = fault;
                        request.fault.printStackTrace();
                    } catch (Throwable fault) {
                        request.responseType = HTTP_RESPONSE_TYPE.RT_500_INTERNAL_SERVER_ERROR;
                        request.fault = fault;
                        request.fault.printStackTrace();
                    } finally {
                        if (token != null) {
                            try {
                                token.object.close();
                            } catch (RemoteException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }

            // all code paths which arrive here send an error response
            isCloseRequested = true;
            if (!isDisconnected) {
                String responseStatus = request.responseType.getResponseStatus();
                HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 0),
                        request.responseType.getResponseCode(), responseStatus);
                response.addHeader(new BasicHeader(HTTP.CONTENT_TYPE, "text/html"));
                response.addHeader(new BasicHeader(HTTP.CONTENT_LEN, Integer.toString(responseStatus.length())));

                try {
                    outStream.write(response.getStatusLine().toString().getBytes());
                    outStream.write("\r\n".getBytes());
                    Header[] headers = response.getAllHeaders();
                    for (int i = 0; i < headers.length; i++) {
                        outStream.write(headers[i].toString().getBytes());
                        outStream.write("\r\n".getBytes());
                    }
                    outStream.write("\r\n".getBytes());
                    outStream.write(responseStatus.getBytes());
                    outStream.flush();
                } catch (Throwable fault) {
                    fault.printStackTrace();
                }
            }
        }

        // ------------------------------------------------------------
        public void run() {
            try {
                if (UntenCacheService.sIsDebugMode) {
                    Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 3);
                    Thread.currentThread().setName(getClass().getName() + ".clientBox[" + clientBoxIndex + "]");
                }

                do {
                    ProcessRequest();
                    ProcessResponse();
                } while (!isCloseRequested && !isStopRequested && request != null && request.fault == null
                        && !request.isHttp10());
            } catch (Throwable fault) {
                fault.printStackTrace();
            } finally {
                if (outStream != null)
                    try {
                        outStream.flush();
                        outStream.close();
                    } catch (Throwable fault) {
                        fault.printStackTrace();
                    }

                if (inStream != null)
                    try {
                        inStream.close();
                    } catch (Throwable fault) {
                        fault.printStackTrace();
                    }

                if (inStreamReader != null)
                    try {
                        inStreamReader.close();
                    } catch (Throwable fault) {
                        fault.printStackTrace();
                    }

                if (inBufferedReader != null)
                    try {
                        inBufferedReader.close();
                    } catch (Throwable fault) {
                        fault.printStackTrace();
                    }

                if (soClient != null && !soClient.isClosed())
                    try {
                        soClient.close();
                    } catch (Throwable fault) {
                        fault.printStackTrace();
                    }

                synchronized (clientBox) {
                    clientBox[clientBoxIndex] = null;
                    clientBoxIndex = -1;
                }
            }
        }
    }
}