在Android APP内部实现一个Http Server——NanoHttpd 简单剖析

  • 前言

     最近公司产品(门禁系统)由安卓这边来实现,需要和宇泛人脸识别终端进行对接,对接过程中说白了,也就是在互相的调接口(这也就是项目做完了敢这么说吧),其实刚刚开始接触这块东西的时候还是挺懵圈的,理顺宇泛的文档,发现好多东西(将人员信息上传至宇泛设备、设置是否允许宇泛设备开门、设置宇泛设备的识别回调地址),这些都是通过Http  post 方式提交数据就好了吗!刚开始看的时候啊,那给我乐的!

在Android APP内部实现一个Http Server——NanoHttpd 简单剖析_第1张图片

  •      正文

     你品“设置宇泛设备的识别回调地址”这句话,你细品,我看到这块文档的时候那是一脸懵圈,这也是我为什么此处标红的原因。

在Android APP内部实现一个Http Server——NanoHttpd 简单剖析_第2张图片

     对他的文档就是这样写的,我看到这块的第一想法就是让后台写个接口,接收人脸识别回调的数据,然后通过UDP或者TCP的形式将消息发送给我这边,和后台、产品一起商量了一天半左右吧,最后考虑到数据传输速度和数据安全方面的问题,还是决定在安卓这边接收数据。

在Android APP内部实现一个Http Server——NanoHttpd 简单剖析_第3张图片

     在Android这边做的话,首先打开我的第二大脑(百度)搜索一下,结果还真的有,看了一下大概三种方法:

           1、golang:简单的了解一下,这个还是比较复杂的,需要配置golang的环境,再进行开发。

           2、AndServer:因为我这个项目中已经有AndServer服务了,是在Android这边开启了一个web端的服务器,为了避免第三方库冲突还是选择其他的吧。

                AndServer官方链接:https://blog.csdn.net/yanzhenjie1003/article/details/64090436/

          3、NanoHttpd:也正是我项目中用到的,这篇文章要讲的东西。

  • NanoHttpd

           官网:https://github.com/NanoHttpd/nanohttpd

      1、NanoHttpd依赖:compile 'org.nanohttpd:nanohttpd:2.2.0'

            使用时NanoHttpd还是比较简单的,只要在build.gradle文件中添加依赖,编译通过就可以了。

      2、开始撸代码吧:

在Android APP内部实现一个Http Server——NanoHttpd 简单剖析_第4张图片

            我们自定义一个MyNanoHttpdServer继承NanoHttpd,实现构造方法:

    public MyNanoHttpdServer(int port) {
        super(port);
    }

           NanoHttpd中有默认有两个构造方法,我们只要实现其中一个构造方法就可以了,构造方法中传入一个参数(端口号),这个参数由个人自行定义(这里建议找一个不常用有好记的端口号)。看一下双参数的构造方法吧:

    public NanoHTTPD(String hostname, int port) {
        this.hostname = hostname;
        this.myPort = port;
        setTempFileManagerFactory(new DefaultTempFileManagerFactory());
        setAsyncRunner(new DefaultAsyncRunner());
    }

        这里就多了一个主机名称,没有必要去考虑那么多了,至于这个构造函数中调用的其他方法是做什么的我这里没有太深入的了解,待后期了解后进行补充!

        通过构造函数创建了MyNanoHttpServer之后,可以直接调用NanoHttpd中的start()方法开启Http Server,看一下start()函数吧(这里因为要创建一个Http Server 其中想必会有耗时操作,所以此处建议在子线程中调用start()函数)。

    public void start(final int timeout, boolean daemon) throws IOException {
        this.myServerSocket = this.getServerSocketFactory().create();
        this.myServerSocket.setReuseAddress(true);

        ServerRunnable serverRunnable = createServerRunnable(timeout);
        this.myThread = new Thread(serverRunnable);
        this.myThread.setDaemon(daemon);
        this.myThread.setName("NanoHttpd Main Listener");
        this.myThread.start();
        while (!serverRunnable.hasBinded && serverRunnable.bindException == null) {
            try {
                Thread.sleep(10L);
            } catch (Throwable e) {
                // on android this may not be allowed, that's why we
                // catch throwable the wait should be very short because we are
                // just waiting for the bind of the socket
            }
        }
        if (serverRunnable.bindException != null) {
            throw serverRunnable.bindException;
        }
    }

        这里第一个参数是设置Http Server连接时超时时间用的,这个没什么好说的。

        第二个参数daemon是干什么用的呢?  简单的看了一下,第二个参数是设置是否启动一个线程来守护当前进程的。这个因个人所需进行设置即可。

       接下来就是很重要的一步了,接收数据,那么数据该怎么接收呢,看了一下,我们的MyNanoHttpdServer可以重写其父类的server(IHTTPSession session)函数来接收数据。

    public Response serve(IHTTPSession session) {
        Map files = new HashMap();
        Method method = session.getMethod();
        if (Method.PUT.equals(method) || Method.POST.equals(method)) {
            try {
                session.parseBody(files);
            } catch (IOException ioe) {
                return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
            } catch (ResponseException re) {
                return newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
            }
        }

        Map parms = session.getParms();
        parms.put(NanoHTTPD.QUERY_STRING_PARAMETER, session.getQueryParameterString());
        return serve(session.getUri(), method, session.getHeaders(), parms, files);
    }

      这里其实就是在帮咱们解析数据,session.getMethod()获取请求方法,因为本人接到的需求是对接别的公司的产品吗,所以看看人间的文档是用的什么请求方式请求的我这边的接口吧,哦,post请求。

在Android APP内部实现一个Http Server——NanoHttpd 简单剖析_第5张图片

       其实我现在关心的根本不是神木请求的问题,我现在看这个函数最下边的一个返回是:

return serve(session.getUri(), method, session.getHeaders(), parms, files);

       是吧,人家已经解析好了,然后用调用了另外一个函数,将解析到的相关信息全都作为参数传进去了是吧,那我此时此刻特别不想重写上边的方法了,我就想重写上边的函数最后一行调用的另外一个serve(... ...)函数,看看这个函数吧,只要这个函数是public修饰的,而且没有被final修饰,那么我就会得宠,额,不不不,是得逞。

       看一下这个方法里边都有些啥吧:

    public Response serve(String uri, Method method, Map headers, Map parms, Map files) {
        return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found");
    }

        只不过这个方法在用的时候画了一条线,说明什么呢,这个方法已经过时了,那和我又有什么关系呢,我只要能用就可以了,对吧。人家给你一姐解析好了你要用到的数据,还封装了现有的方法供你进行重写使用,那还别扭啥啊,这又何乐而不为呢是不是。

        接下来看看我自己重写的serve函数中的大体逻辑吧,因为此处涉及和其他公司进行对接,为了避免泄漏我这边不该泄漏的东西,就不贴出来了,简单的了解一下就好,你们自己做的时候,简单的看一下,自然就会明白。

     首先我们要先判断一下请求方法,是不是post请求对吧:

    method.equals(Method.POST)

        直接对比就好了,Method是NanoHTTPD类中的枚举,可以直接调用。请求方法比对完了之后,就开始要取出客户端请求时的参数了。

    String personData = files.get("postData");

         files里边就把客户端请求的信息全都封装成一个map数组,因为这里我们用的是post请求吗,所以这里我们就去获取post过来的数据,files.get("postData"),获取到数据之后,每个人要做的工作就不一样了。好了,大概也就是这样了,其实里边并没有太多的东西,毕竟是使用别人已经封装好的库吗,还是很简单的。

        简单的把我这边自己写的代码粘出来看一下吧,解析的部分就因人而异了:

import com.blankj.utilcode.util.StringUtils;
import com.tehike.studydemo.util.WriteLogToFile;

import java.io.IOException;
import java.util.Map;

import fi.iki.elonen.NanoHTTPD;

/**
 * 
 * Created by small_world
 * QQ:1529442917  mail:[email protected]
 * Time :2020/1/3 14:30
 * data :
 * 
 * ********************************我佛慈悲**************************************
 * **                              _oo0oo_                               **
 * **                             o8888888o                              **
 * **                             88" . "88                              **
 * **                             (| -_- |)                              **
 * **                             0\  =  /0                              **
 * **                           ___/'---'\___                            **
 * **                        .' \\\|     |// '.                          **
 * **                       / \\\|||  :  |||// \\                        **
 * **                      / _ ||||| -:- |||||- \\                       **
 * **                      | |  \\\\  -  /// |   |                       **
 * **                      | \_|  ''\---/''  |_/ |                       **
 * **                      \  .-\__  '-'  __/-.  /                       **
 * **                    ___'. .'  /--.--\  '. .'___                     **
 * **                 ."" '<  '.___\_<|>_/___.' >'  "".                  **
 * **                | | : '-  \'.;'\ _ /';.'/ - ' : | |                 **
 * **                \  \ '_.   \_ __\ /__ _/   .-' /  /                 **
 * **            ====='-.____'.___ \_____/___.-'____.-'=====             **
 * **                              '=---='                               **
 * ************************************************************************
 * **                佛祖保佑      镇类之宝       永无bug                **
 * ************************************************************************                                    丶
 */

public class MyNanoHttpdServer extends NanoHTTPD {

    //实现父类的构造方法
    public MyNanoHttpdServer(int port) {
        super(port);
    }

    //正式开启服务
    public void start() {
        try {
            start(NanoHTTPD.SOCKET_READ_TIMEOUT, true);

            //向日志文件中写入接收消息的接口已经打开
            WriteLogToFile.info("The face recognition callback interface has been opened, the port number is 9999.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Response serve(IHTTPSession session) {
        //这个就是之前分析,重写父类的一个参数的方法,
        //这里边已经把所有的解析操作已经在这里执行了
        return super.serve(session);
    }

    @Override
    public Response serve(String uri, Method method, Map headers, Map parms, Map files) {
        //这就是上边的serve方法最后一行调用的那个过时的方法,这里简单的做个判断就好了
        if (!method.equals(Method.POST)) {//判断请求方式是否争取
            return newFixedLengthResponse("the request method is incoorect");
        }
        if (!StringUtils.equalsIgnoreCase(uri, "callBackUrl")) {//判断uri是否正确
            return newFixedLengthResponse("the request uri is incoorect");
        }
        String personData = files.get("postData");
        if (StringUtils.isEmpty(personData)) {//判断post过来的数据是否正确
            return newFixedLengthResponse("postData is null");
        }
        //判断完了开始解析数据,如果是你想要的数据,那么你就给返回一个正确的格式就好了
        //举个栗子:return newFixedLengthResponse("{\"result\":0,\"success\":true}");
        return super.serve(uri, method, headers, parms, files);
    }
}

        至于上边的newFixedLengthResponse("")函数是干啥用的呢,简单的了解一下吧:

    public static Response newFixedLengthResponse(String msg) {
        return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, msg);
    }

        点进去之后你会发现,他又调用了本类中,该函数本身的重载方法,返回了一个Status.OK,这个肯定就是200了,也就是说,只要是我们接收到数据之后,我们自己手动返回请求结果的时候,那么他底层就认为这个请求时成功的。

在Android APP内部实现一个Http Server——NanoHttpd 简单剖析_第6张图片

  • 小结

        其实整个过程中自己也是看着BanoHttpd官方文档以及参考别人的博客,一边摸索学习,因为项目比较着急的原因,这里只是将简单的流程和原理在这里介绍了一下,后期可能在本篇文章的基础上扩展内容,也或许写一篇新的文章,因为毕竟觉得这块东西还是值得“深入”一下的

  • 题外话

        对接其他公司的产品这已经是我们这个产品2.0版本干的事了,1.0版本对接人脸识别设备的时候(当时并没有考虑到后期使用外部的人脸识别设备,因为给保密机构做产品还是自己的东西用着放心对吧,但是谁能想到1.0版本用到的人脸识别设备用到的硬件的厂家因为之前的贸易战收到了影响,不能及时供货,那时候订单还是比较多的,所以就选择了外部产品这个应急之策),是通过收发TCP消息进行传输数据的(因为1.0版本对接的是公司同事自己研发的设备,所以这块的协议我们内部可以自行商议解决),我这边是自定义的http server(或许只需叫做server),同事的人脸识别设备是自定义的http client(同理,只需叫client)来进行客户端和服务器端的交互以及数据的传输,我们其实并不需要使用http协议,我们可以写个安卓程序,使用socket和serversocket的程序,用不同的设备运行这个程序,利用我们自己定好的标识符即可实现交互和传输了。

       讲一下Http的特点:

         无连接:处理完请求并且得到应答后即断开链接

         媒体独立:任何类型的数据都可通过http协议完成

         无状态:不记录前面的状态信息

  • 结束语、

        好了,到这里就结束了,好久没有写博客了,有些言语不对的地方还希望大家能够见谅,有什么不对的地方还希望大神能够多多指点、指教。这是我2020年的第一篇博客,最近几天可能闲下来了,可能整理一下简历(每年年底的时候我都会整理一次自己的简历,看看一年中自己成长了哪些,为自己定个方向),还可能会写几篇博客吧,毕竟自己学到的东西,一是为了能够做个笔记,二是分享给大家供大家参考。

       最后还是要说一句,有什么不对的地方希望能得到批评指正。

你可能感兴趣的:(移动开发,Android,Server,NanoHttpd)