NanoHttpd嵌入式服務(wù)器

基于NanoHttpd的Android視頻服務(wù)器開發(fā)

說明
NanoHttpd是個(gè)很強(qiáng)大的開源庫,僅僅用一個(gè)Java類,就實(shí)現(xiàn)了一個(gè)輕量級(jí)的 Web Server,可以非常方便地集成到Android應(yīng)用中去,讓你的App支持 HTTP GET, POST, PUT, HEAD 和 DELETE 請(qǐng)求。

為了演示它的功能,我利用該庫搭建了一個(gè)簡(jiǎn)單地Android視頻服務(wù)器,可以通過PC瀏覽器遠(yuǎn)程播放Android手機(jī)存儲(chǔ)器中的mp4視頻文件。

最基本的使用

1)下載該庫并添加到你的Android工程中,就可以使用NanoHTTPD類了,該類最重要的三個(gè)函數(shù),一個(gè)是start(),一個(gè)是 stop(),用于啟動(dòng)和停止Web Server,再一個(gè)就是serve(),該函數(shù)就是收到瀏覽器的請(qǐng)求后的回調(diào)函數(shù),可以在該函數(shù)內(nèi)部給瀏覽器返回響應(yīng)的HTTP頁面。

下面是一個(gè)最簡(jiǎn)單的對(duì)所有請(qǐng)求都返回404錯(cuò)誤的示例:

public class VideoServer extends NanoHTTPD {
  public VideoServer(int port) {
    super(port);
  }
  @Override
  public Response serve(IHTTPSession session) {     
    StringBuilder builder = new StringBuilder();
    builder.append("<!DOCTYPE html><html><body>");      
    builder.append("404 -- Sorry, Can't Found "+ session.getUri() + " !");      
    builder.append("</body></html>\n");
    return new Response(builder.toString());
  }
}

其中,IHTTPSession類提供了一系列的接口,用來判斷瀏覽器的請(qǐng)求內(nèi)容,包括:GET/PUT類型、請(qǐng)求的URL等等,你可以以此為判斷針對(duì)不同的請(qǐng)求完成服務(wù)或者返回相應(yīng)的頁面。

2)瀏覽器中播放視頻
要想通過瀏覽器直接播放視頻,目前最常見的有兩種方式,一種是采用Flash播放器,另一種利用HTML5標(biāo)簽,本文就是采用了HTML5標(biāo)簽實(shí)現(xiàn)的。

下面就是Android端收到HTTP請(qǐng)求之后返回的HTML5頁面,參考: 《HTML5教程》

<!DOCTYPE HTML>
<html>
<body>

<video width="320" height="240" controls="controls">
<source src="/storage/emulated/0/movie.mp4" type="video/mp4">
    Your browser doestn't support HTML5
</video>
</body>

</html>

瀏覽器收到該HTML5頁面后,會(huì)進(jìn)一步請(qǐng)求<source>標(biāo)簽給出的視頻地址,這時(shí)Android端就需要通過字節(jié)流的形式將本地的視頻文件發(fā)送給瀏覽器,代碼如下:

public Response responseVideoStream(IHTTPSession session,String videopath) {
  try {
    FileInputStream fis = new FileInputStream(videopath);
    return new NanoHTTPD.Response(Status.OK, "video/mp4", fis);
  } 
  catch (FileNotFoundException e) {     
    e.printStackTrace();
    return new Response("Error");
  } 
}

注意問題

1.簽名證書 generating an self signed ssl certificate

keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1 -validity 9999

這個(gè)應(yīng)該是https協(xié)議需要的東西

  1. 啟動(dòng)方式
    在Android里面服務(wù)器的啟動(dòng)使用
//SimpleServer 繼承自NanoHTTPD
  SimpleServer simpleServer = new SimpleServer();
        try {
            simpleServer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

Java里面這么啟動(dòng)

 public static void main(String[] args) {
        ServerRunner.run(SimpleServer.class);
    }

最基本的結(jié)構(gòu)僅僅3個(gè)類

Paste_Image.png

ServerRunner

public class ServerRunner {

    /**
     * logger to log to.
     */
    private static final Logger LOG = Logger.getLogger(ServerRunner.class.getName());

    public static void executeInstance(NanoHTTPD server) {
        try {
            server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
        } catch (IOException ioe) {
            System.err.println("Couldn't start server:\n" + ioe);
            System.exit(-1);
        }

        System.out.println("Server started, Hit Enter to stop.\n");

        try {
            System.in.read();
        } catch (Throwable ignored) {
        }

        server.stop();
        System.out.println("Server stopped.\n");
    }

    public static <T extends NanoHTTPD> void run(Class<T> serverClass) {
        try {
            executeInstance(serverClass.newInstance());
        } catch (Exception e) {
            ServerRunner.LOG.log(Level.SEVERE, "Cound nor create server", e);
        }
    }
}

SimpleServer

package fi.iki.elonen;


import java.util.Map;
import java.util.logging.Logger;

import fi.iki.elonen.NanoHTTPD;

public class SimpleServer extends NanoHTTPD {

    /**
     * logger to log to.
     */
    private static final Logger LOG = Logger.getLogger(SimpleServer.class.getName());

    public static void main(String[] args) {
        ServerRunner.run(SimpleServer.class);
    }

    public SimpleServer() {
        super(8999);
    }

    @Override
    public Response serve(IHTTPSession session) { // 此方法等同于TomCat總控制器,因?yàn)闆]有向Struts2那樣映射過,故請(qǐng)求跳轉(zhuǎn)的url需要我們自己處理,此方法會(huì)接收所有請(qǐng)求
        Method method = session.getMethod();
        String uri = session.getUri();
        SimpleServer.LOG.info(method + " '" + uri + "' ");

        String msg = "<html><body><h1>Hello server</h1>\n";
        Map<String, String> parms = session.getParms();
        if (parms.get("username") == null) {
            msg += "<form action='?' method='get'>\n" + "  <p>Your name: <input type='text' name='username'></p>\n" + "</form>\n";
        } else {
            msg += "<p>Hello, " + parms.get("username") + "!</p>";
        }

        msg += "</body></html>\n";

        return newFixedLengthResponse(msg);
    }
}




NanoHttpd(核心類)

太長(zhǎng)了不在列舉了

由于 每次經(jīng)過 Response serve會(huì)多請(qǐng)求一次
該是瀏覽器多請(qǐng)求了一個(gè) /favicon.ico
所以這里注意過濾一下

@Override
    public Response serve(IHTTPSession session) { // 此方法等同于TomCat總控制器,因?yàn)闆]有向Struts2那樣映射過,故請(qǐng)求跳轉(zhuǎn)的url需要我們自己處理,此方法會(huì)接收所有請(qǐng)求
        Method method = session.getMethod();
        String uri = session.getUri();
        SimpleServer.LOG.info(method + " '" + uri + "' ");

        String msg = "<html><body><h1>Hello server</h1>\n";
        Map<String, String> parms = session.getParms();
        if (parms.get("username") == null) {
            msg += "<form action='?' method='get'>\n" + "  <p>Your name: <input type='text' name='username'></p>\n" + "</form>\n";
        } else {
            msg += "<p>Hello, " + parms.get("username") + "!</p>";
        }

        msg += "</body></html>\n";
        Log.d("wl", msg);

        if (!uri.contains("favicon.ico")) {
            callBack.changeText(msg);
        }

        return newFixedLengthResponse(msg);
    }

問題

Post請(qǐng)求的參數(shù)接收不到

public MyServer() throws IOException {
        super(PORT);
        File f;
        f = new File("/storage/sdcard0/www");
        if (f.canWrite()) {
            rootDir = "/storage/sdcard0/www";
            System.out.println("rootDir = " + rootDir);
        }
        else {
            f = new File("/storage/sdcard1/www");
            if (f.canWrite()) {
                rootDir = "/storage/sdcard1/www";
                System.out.println("rootDir = " + rootDir);
            }
            else {
                rootDir = "/storage/sdcard0";
                System.out.println("set rootDir default " + rootDir);
            }
        }

        start(NanoHTTPD.SOCKET_READ_TIMEOUT);
    }

文件下載(無文件返回名)

private Response newFixedFileResponse(File file, String mime) throws FileNotFoundException {
        Response res;
        res = newFixedLengthResponse(Response.Status.OK, mime, new FileInputStream(file), (int) file.length());
        res.addHeader("Accept-Ranges", "bytes");
        return res;
    }


文件下載

Server


public class SimpleServer extends NanoHTTPD { 

    public static void main(String[] args) {
        ServerRunner.run(SimpleServer.class);
    }

    public SimpleServer() {
        super(8990);
    }

    @Override
    public Response serve(IHTTPSession session) {
        Method method = session.getMethod();
        String uri = session.getUri();
        String username = session.getQueryParameterString();
        
        System.out.println("method "+method+" uri "+uri+" "+username);
 
   InputStream inputStream;
   Response response = null;
  try {
   inputStream = new FileInputStream(new File("/Users/ruulai/Desktop/some/test.java"));
    response = newChunkedResponse(Status.OK, "application/octet-stream", inputStream);//這代表任意的二進(jìn)制數(shù)據(jù)傳輸。
  } catch (FileNotFoundException e) {
   e.printStackTrace();
  }
  
   response.addHeader("Content-Disposition", "attachment; filename="+"test.java");      
      return response;
       
    }
}

文件上傳(稍微麻煩了點(diǎn))

package fi.iki.elonen;

import android.content.Context;
import android.util.Log;

import com.ruulai.littleserver.IOUtils;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.FileUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

import fi.iki.elonen.NanoHTTPD.Response.Status;

    //服務(wù)端
    public  class TestServer extends NanoHTTPD {
        private Context mContext;
        public TestServer(Context context) {
            super(9999);
            uploader = new NanoFileUpload(new DiskFileItemFactory());
            mContext = context;
        }
           public NanoFileUpload uploader;
           public Response response = newFixedLengthResponse(""); //返回客戶端 Response

            public String uri;

            public Method method;

            public Map<String, String> header;

            public Map<String, String> parms;

            public Map<String, List<FileItem>> files;

            public Map<String, List<String>> decodedParamters; // {name=[xiaoming], password=[abcd]}

            public String queryParameterString; //格式如 name=xiaoming&password=abcd

        @Override
        public Response serve(IHTTPSession session) {
            // 測(cè)試發(fā)現(xiàn),無論是POST還是GET都會(huì)多執(zhí)行一次 GET /favicon.ico ,且同樣拿不到參數(shù)
            this.uri = session.getUri();   //訪問的請(qǐng)求如 http://192.168.31.118:8192/uploadFile1 ,但是 this.uri 為  /uploadFile1
            this.method = session.getMethod();
            this.header = session.getHeaders();
            this.parms = session.getParms();
            System.out.println("方法是:" + method+" "+uri);
            if (NanoFileUpload.isMultipartContent(session)) {
                try {
                    if ("/uploadFile1".equals(this.uri)) {
//                        session.getHeaders().put("content-length", "AA");
                        files = uploader.parseParameterMap(session);
                        FileItem fileItem = files.get("upfile").get(0);

                        InputStream inputStream = fileItem.getInputStream();
                        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

                        FileUtils.copyInputStreamToFile(bufferedInputStream, new File("/sdcard/laji.java"));
                        Log.d("wl", "文件保存成功");


                    }


                } catch (Exception e) {
                    this.response.setStatus(Status.INTERNAL_ERROR);
                    e.printStackTrace();
                }
            }


            this.queryParameterString = session.getQueryParameterString(); //HttpPost post = new HttpPost("http://192.168.31.118:8192/uploadFile1?name=xiaoming&password=abcd");可以獲取Post請(qǐng)求參數(shù)
            this.decodedParamters = decodeParameters(queryParameterString);

            String usernmae = this.parms.get("username");
            System.out.println(queryParameterString+" "+decodedParamters+" "+usernmae);
//            name=xiaoming&password=abcd {name=[xiaoming], password=[abcd]}

            String html = IOUtils.readFile(mContext);
//            return this.response; // =========》  返回給客戶端
            return  newFixedLengthResponse(html) ; // =========》  返回給客戶端
        }


        
    }



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,769評(píng)論 25 709
  • 外婆一共生了三個(gè)兒子兩個(gè)女兒,媽媽排行老二,老大是大舅舅,底下還有二舅小姨,小舅舅最小,算是外公外婆老來得子,只比...
    千尋蓮子閱讀 518評(píng)論 3 2
  • 第一步: 刪除CocoaPod相關(guān)的文件: 對(duì),這些都要?jiǎng)h除掉。 第二步: 打開 工程名字.xcodeproj 文...
    阿茲爾閱讀 277評(píng)論 0 0
  • 若有來生,你我還會(huì)選擇相遇嗎? 森問:“若在相遇,你還能在人群中一眼認(rèn)出我嗎?”蝶遲疑了一會(huì)兒,口氣中帶著點(diǎn)點(diǎn)的悲...
    一個(gè)失憶的人閱讀 377評(píng)論 0 0
  • 很多人說,初夏時(shí)節(jié),時(shí)常多雨???,在北京,雨有時(shí)候說來就來,有時(shí)候很久很久都不下一滴。 幾天悶熱的天氣過后總能有一...
    瀟筠閱讀 914評(píng)論 2 4

友情鏈接更多精彩內(nèi)容