AndroidでHttpServerを実現

11725 ワード

最近のプロジェクトではAndroidを1つのサーバとしてリアルタイムでデータを受信する機能を作るので、この時はAndroidローカルのマイクロサーバを作ります.
では、この時私はまずspring bootを思い出しました.彼はサーバーのフレームワークだからです.しかし、実際にはこのような大規模なサーバフレームワークが使えず、構成が面倒です.だから、Ijetty、NanoHttpd、Android Asyncの3つのフレームワークを見つけました.いずれも比較的小型で、Androidに適しています.
対照的に、Ijettyは複雑すぎて、解決しにくい問題をわけがわからず報告してしまうので、捨ててしまいました.
Ijettyについて詳しく研究していないので、NanoHttpdとAndroidAsyncに重点を置いています.では、まず次の2つのメリットとデメリットについて説明します.
1.NanoHttpdはBIOがベースパッケージングのフレームワークであり、Android AsyncはNIOがベースパッケージングのものであり、他は同じであり、実際にAndroid AsyncはNanoHttpdフレームワークに倣って書かれている.だから、ある意味ではAndroid AsyncはNanoHttpdの最適化版で、もちろん具体的な応用シーンを見て辛いです.
2.NanoHttpdはHttpServerにしか使えませんが、Android AsyncはHttpServerのアプリの他にwebSocket、HttpClientなどにも使えますが、その中でもAndroid Asyncから離脱したIonのライブラリが有名です.
3.NanoHttpd下位処理に含まれる戻り状態コード(例えば、200、300、400、500など)が比較的多い.しかし、筆者がAndroid Asyncのソースコードを読んだところ、Android Asyncの下位パッケージが返すステータスコードは2種類しかない:200、404、ちょうど筆者がこのピットを発見した(後述、OPTIONSの例)
具体的な使い方を見てみましょう.
1.NanoHttpd:NanoHttpdのフレームワークは実際には単一のファイルであるため、githubに直接ダウンロードすることができ、ダウンロードアドレスにダウンロードされたファイルがあれば、このファイルを継承してクラスを書くことができ、具体的には以下の通りである.
public class HttpServer extends NanoHTTPD {
    private static final String TAG = "HttpServer";

    public static final String DEFAULT_SHOW_PAGE = "index.html";
    public static final int DEFAULT_PORT = 9511;//       ,    1024-65535;1-1024       ,1024-65535      

    public enum Status implements Response.IStatus {
        REQUEST_ERROR(500, "    "),
        REQUEST_ERROR_API(501, "       "),
        REQUEST_ERROR_CMD(502, "    ");

        private final int requestStatus;
        private final String description;

        Status(int requestStatus, String description) {
            this.requestStatus = requestStatus;
            this.description = description;
        }

        @Override
        public String getDescription() {
            return description;
        }

        @Override
        public int getRequestStatus() {
            return requestStatus;
        }
    }

    public HttpServer() {//     
        super(DEFAULT_PORT);
    }

    @Override
    public Response serve(IHTTPSession session) {
        String uri = session.getUri();
        Map headers = session.getHeaders();

        //    post     ,              http://blog.csdn.net/obguy/article/details/53841559
        try {
            session.parseBody(new HashMap());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ResponseException e) {
            e.printStackTrace();
        }
        Map parms = session.getParms();
        try {
            LogUtil.d(TAG, uri);

//  uri    ,     ,             
            if (checkUri(uri)) {
                //          
                if (headers != null) {
                    LogUtil.d(TAG, headers.toString());
                }
                if (parms != null) {
                    LogUtil.d(TAG, parms.toString());
                }

                if (StringUtil.isEmpty(uri)) {
                    throw new RuntimeException("        ");
                }

                if (Method.OPTIONS.equals(session.getMethod())) {
                    LogUtil.d(TAG, "OPTIONS     ");
                    return addHeaderResponse(Response.Status.OK);
                }

                switch (uri) {
                    case "/test": {//  2
                       //                          
                        return getXXX(parms);
                    }
                    default: {
                        return addHeaderResponse(Status.REQUEST_ERROR_API);
                    }
                }
            } else {
                //           
                String filePath = getFilePath(uri); //   url      

                if (filePath == null) {
                    LogUtil.d(TAG, "sd     ");
                    return super.serve(session);
                }
                File file = new File(filePath);

                if (file != null && file.exists()) {
                    LogUtil.d(TAG, "file path = " + file.getAbsolutePath());
//       mimeType: image/jpg, video/mp4, etc
                    String mimeType = getMimeType(filePath); 

                    Response res = null;
                    InputStream is = new FileInputStream(file);
                    res = newFixedLengthResponse(Response.Status.OK, mimeType, is, is.available());
//        (      h5  ,        )
                    response.addHeader("Access-Control-Allow-Headers", allowHeaders);
                    response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, HEAD");
                    response.addHeader("Access-Control-Allow-Credentials", "true");
                    response.addHeader("Access-Control-Allow-Origin", "*");
                    response.addHeader("Access-Control-Max-Age", "" + 42 * 60 * 60);

                    return res;
                } else {
                    LogUtil.d(TAG, "file path = " + file.getAbsolutePath() + "      ");
                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        //         
        return addHeaderRespose(Status.REQUEST_ERROR);
    }

上記の例によれば、主に以下の点を説明する:1)postでもgetでも、他のリクエストでも、フィルタリングが必要な場合は自分で処理するリクエストを受信することができる.
2)上記の処理でpostパラメータが受信できない問題に注意し、参照リンクをコードコメントに添付しましたので、ご参照ください.
3)要求にインタフェースとhtmlなどの静的リソースがある場合、uriで識別できるなどの2つの要求を区別することに注意する.もちろん、戻りはすべてストリームの形式で使用することができ、APIメソッドnewFixedLengthResponse()を呼び出すことができます.
4)筆者は、Androidがh 5と連動する可能性があるため、ドメインを越えた後のデバッグが便利であることを提案した.もちろん、一部のシーンも無視でき、個人のニーズを見ることができる.方法はすでに以上のコードに書かれています.
5)もちろん最後に最も重要なのは、オンとオフのコードに違いありません.
/**
 *            
 */
public static void startLocalChooseMusicServer() {

    if (httpServer == null) {
        httpServer = new HttpServer();
    }

    try {
        //   web  
        if (!httpServer.isAlive()) {
            httpServer.start();
        }
        Log.i(TAG, "The server started.");
    } catch (Exception e) {
        httpServer.stop();
        Log.e(TAG, "The server could not start. e = " + e.toString());
    }

}

/**
 *       
 */
public static void quitChooseMusicServer() {
    if (httpServer != null) {
        if (httpServer.isAlive()) {
            httpServer.stop();
            Log.d(TAG, "          ");
        }
    }
}

2 AndroidAsyncをもう一度見てみましょう:このフレームワークは比較的に面白くて、機能も多くて、本文はHttpServerの方面の関連知識を率直に言って、残りは表を押さないでください.
古いルール、まず使い方を言います:Gradleに参加します:
dependencies {
    compile 'com.koushikdutta.async:androidasync:2.2.1'
}

コード例:(ここではドメイン間処理は行われていませんが、必要に応じて前の例に従って処理してください)
public class NIOHttpServer implements HttpServerRequestCallback {

    private static final String TAG = "NIOHttpServer";

    private static NIOHttpServer mInstance;

    public static int PORT_LISTEN_DEFALT = 5000;

    AsyncHttpServer server = new AsyncHttpServer();

    public static NIOHttpServer getInstance() {
        if (mInstance == null) {
            //     ,        
            synchronized (NIOHttpServer.class) {
                if (mInstance == null) {
                    mInstance = new NIOHttpServer();
                }
            }
        }
        return mInstance;
    }

//  nanohttpd   
    public static enum Status {
        REQUEST_OK(200, "    "),
        REQUEST_ERROR(500, "    "),
        REQUEST_ERROR_API(501, "       "),
        REQUEST_ERROR_CMD(502, "    "),
        REQUEST_ERROR_DEVICEID(503, "      ID"),
        REQUEST_ERROR_ENV(504, "        ");

        private final int requestStatus;
        private final String description;

        Status(int requestStatus, String description) {
            this.requestStatus = requestStatus;
            this.description = description;
        }

        public String getDescription() {
            return description;
        }

        public int getRequestStatus() {
            return requestStatus;
        }
    }

    /**
 *       
 */
    public void startServer() {
//          ,           
        server.addAction("OPTIONS", "[\\d\\D]*", this);
        server.get("[\\d\\D]*", this);
        server.post("[\\d\\D]*", this);
        server.listen(PORT_LISTEN_DEFALT);
    }

    @Override
    public void onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
        Log.d(TAG, "   ,  ");
        String uri = request.getPath();
//     header     ,      
        Multimap headers = request.getHeaders().getMultiMap();

        if (checkUri(uri)) {//          
            //  :       post        ,      
            Multimap parms = (( AsyncHttpRequestBody)request.getBody()).get();
            if (headers != null) {
                LogUtil.d(TAG, headers.toString());
            }
            if (parms != null) {
                LogUtil.d(TAG, "parms = " + parms.toString());
            }

            if (StringUtil.isEmpty(uri)) {
                throw new RuntimeException("        ");
            }

            if ("OPTIONS".toLowerCase().equals(request.getMethod().toLowerCase())) {
                LogUtil.d(TAG, "OPTIONS     ");
                addCORSHeaders(Status.REQUEST_OK, response);
                return;
            }

            switch (uri) {
                    case "/test": {//  2
                       //                          
                        return getXXX(parms);
                    }
                    default: {
                        return addHeaderResponse(Status.REQUEST_ERROR_API);
                    }
                }
        } else {
            //            
            String filePath = getFilePath(uri); //   url      

            if (filePath == null) {
                LogUtil.d(TAG, "sd     ");
                response.send("sd     ");
                return;
            }
            File file = new File(filePath);

            if (file != null && file.exists()) {
                Log.d(TAG, "file path = " + file.getAbsolutePath());

                response.sendFile(file);// nanohttpd      

            } else {
                Log.d(TAG, "file path = " + file.getAbsolutePath() + "      ");
            }
        }
    }
}

上の例によると、主に以下の点を述べる:{大体apiの使い方だろうこの類の}1)例えば:server.addAction(「OPTIONS」,「[dD],this)は、汎用的なフィルタ要求の方法である.1番目のパラメータは、要求の方法であり、例えば、「OPTIONS」、「DELETE」、「POST」、「GET」など(大文字で注意)、2番目のパラメータはuriをフィルタする正規表現であり、ここではすべてのuriをフィルタし、3番目はコールバックパラメータである.server.post("[\d\D]", this)、server.get(「[dD]*」,this)これは前の方法の特例です.server.Listen(PORT_LISTEN_DEFALT)これはリスニングポートです.2) request.getHeaders().getMultiMap()headerパラメータを取得する場所ですので、ご注意ください.3)(( AsyncHttpRequestBody)request.getBody()).get()という場所はpostリクエストのパラメータを取得する場所です.4)静的リソースを取得するコードは,コールバック法onResponseのelseであり,例は上述の通りである.5)OPTIONSのピットについてお話ししますと、Android Asyncというフレームワークでカプセル化されているhttpに戻るステータスコードは2種類しかないので、フィルタリング方法にOPTIONSのようなリクエスト方法が含まれていない場合、実際にクライアントに返すhttpステータスコードは400であり、ブラウザに反映されるエラーメッセージがドメインをまたぐ問題であることは、長い間探していましたので、ご注意ください.
まとめ:1)同じページ:NanoHttpd消費時間:1.4 s Android Async消費時間:1.4 sしかし、2回目に入ったとき、Android Asyncの消費時間は明らかに1つ目より少なくなった.筆者はAndroid Asyncの底層がいくつかの処理をしたからだと推測している.
2)apiから分析すると,NanoHttpdの使い方が便利で,伝達されたパラメータを取得するapiが使いやすい.AndroidAsyncのapiはparamsの取得など比較的複雑です.
3)シーンから分析すると,コンカレント量の高い需要が必要であれば,Android Asyncを用いなければならない.しかし、必要でなければ、具体的に分析します.