In a recent project, because Android is used as a server to perform a real-time data reception function, it is necessary to make an Android local micro server. At this time, I first thought of spring boot, because it is a server framework. But in fact, we don't need such a large server framework at all, and it's too troublesome to configure them. So, I found three frameworks: Ijetty, NanoHttpd and AndroidAsync, which are relatively small and suitable for Android. After comparison, Ijetty is too complicated to use, and it will inexplicably report some problems that are not easy to solve, so it was abandoned. Since I didn't study Ijetty in detail, I focused on NanoHttpd and AndroidAsync; So let's first talk about the advantages and disadvantages of the two: 1. NanoHttpd is a framework with BIO as the underlying encapsulation, while AndroidAsync is encapsulated with NIO as the underlying encapsulation. The rest is the same, and in fact AndroidAsync is written based on the NanoHttpd framework. So, in a sense, AndroidAsync is an optimized version of NanoHttpd, but of course it also depends on the specific application scenario. 2. NanoHttpd can only be used for HttpServer, but AndroidAsync can be used in webSocket, HttpClient, etc. in addition to HttpServer applications. Among them, the Ion library separated from AndroidAsync is also relatively famous. 3.NanoHttpd's underlying processing includes many return status codes (for example: 200, 300, 400, 500, etc.); but after reading the source code of AndroidAsync, I found that there are only two status codes returned by the underlying encapsulation of AndroidAsync: 200 and 404. I just found this pit (the example of OPTIONS will be mentioned below) Let’s take a look at the specific usage below. 1. Let’s talk about NanoHttpd first: Because the NanoHttpd framework is actually a single file, you can download it directly from GitHub. Download address With the downloaded file, you can inherit this file and write a class as follows: - 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; //This parameter can be defined at will, and 1024-65535 is the best definition; 1-1024 is the common system port, and 1024-65535 is the non-system port
-
- public enum Status implements Response.IStatus {
- REQUEST_ERROR(500, "Request failed" ) ,
- REQUEST_ERROR_API(501, "Invalid request API" ),
- REQUEST_ERROR_CMD(502, "Invalid command" );
-
- 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() { // Initialize port
- super(DEFAULT_PORT);
- }
-
- @Override
- public Response serve(IHTTPSession session) {
- String uri = session.getUri();
- Map<String, String> headers = session.getHeaders();
-
- //Problem of not receiving post parameters, http://blog.csdn.net/obguy/article/details/53841559
- try {
- session.parseBody(new HashMap<String, String>());
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ResponseException e) {
- e.printStackTrace();
- }
- Map<String, String> parms = session.getParms();
- try {
- LogUtil.d(TAG, uri);
-
- //Judge the legitimacy of uri, custom method, this is the method to determine whether it is an interface
- if (checkUri(uri)) {
- //For interface processing
- if (headers != null ) {
- LogUtil.d(TAG, headers.toString());
- }
- if (parms != null ) {
- LogUtil.d(TAG, parms.toString());
- }
-
- if (StringUtil.isEmpty(uri)) {
- throw new RuntimeException( "Unable to obtain request address" );
- }
-
- if (Method.OPTIONS.equals(session.getMethod())) {
- LogUtil.d(TAG, "OPTIONS exploratory request" );
- return addHeaderResponse(Response.Status.OK);
- }
-
- switch (uri) {
- case "/test" : {//Interface 2
- //This method includes encapsulating the returned interface request data and handling exceptions and cross-domain
- return getXXX(parms);
- }
- default : {
- return addHeaderResponse(Status.REQUEST_ERROR_API);
- }
- }
- } else {
- //For the processing of static resources
- String filePath = getFilePath(uri); // Get the file path based on the url
-
- if (filePath == null ) {
- LogUtil.d(TAG, "sd card not found" );
- return super.serve(session);
- }
- File file = new File(filePath);
-
- if (file != null && file.exists()) {
- LogUtil.d(TAG, "file path = " + file.getAbsolutePath());
- //Return mimeType based on file name: 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());
- //The following are the cross-domain parameters (because they are usually coordinated with h5, so they must be set)
- 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() + " resource does not exist" );
- }
-
- }
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- //Self-encapsulated return request
- return addHeaderRespose(Status.REQUEST_ERROR);
- }
Based on the above example, the following points are mainly made: 1) All requests can be received, whether it is post or get, or other requests. If filtering is required, handle it yourself; 2) Note that the problem of not receiving post parameters handled above has been given a reference link in the code comments, please refer to it; 3) If the request contains both an interface and a static resource (such as HTML), then be careful to distinguish the two requests, for example, you can use URI to identify them; of course, both can be returned in the form of a stream, and both can call the API method newFixedLengthResponse(); 4) The author suggests that you should first deal with the cross-domain issue, because Android may be debugged with h5, so it is easier to debug after setting up the cross-domain. Of course, some scenarios can be ignored, depending on personal needs; the method has been written in the above code; 5) Of course, the most important thing is the opening and closing code: - /**
- * Enable local web page song request service
- */
- public static void startLocalChooseMusicServer() {
-
- if (httpServer == null ) {
- httpServer = new HttpServer();
- }
-
- try {
- // Start the web service
- 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());
- }
-
- }
-
- /**
- * Disable local services
- */
- public static void quitChooseMusicServer() {
- if (httpServer != null ) {
- if (httpServer.isAlive()) {
- httpServer.stop();
- Log.d(TAG, "Close the LAN song request service" );
- }
- }
- }
2 Let’s take a look at AndroidAsync: This framework is quite interesting and has many functions. This article will only talk about the relevant knowledge of HttpServer and leave the rest for later reference. As usual, let's talk about usage first: Add in Gradle: - dependencies {
-
- compile 'com.koushikdutta.async:androidasync:2.2.1'
-
- }
Code example: (Cross-domain is not handled here. If necessary, please handle it according to the previous example) - 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 ) {
- //Add class lock to ensure initialization only once
- synchronized (NIOHttpServer.class) {
- if (mInstance == null ) {
- mInstance = new NIOHttpServer();
- }
- }
- }
- return mInstance;
- }
-
- //Follow the writing method of nanohttpd
- public static enum Status {
- REQUEST_OK(200, "Request successful" ) ,
- REQUEST_ERROR(500, "Request failed" ) ,
- REQUEST_ERROR_API(501, "Invalid request API" ),
- REQUEST_ERROR_CMD(502, "Invalid command" ) ,
- REQUEST_ERROR_DEVICEID(503, "Mismatched device ID" ) ,
- REQUEST_ERROR_ENV(504, "Mismatched service environment" );
-
- 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;
- }
- }
-
- /**
- * Enable local service
- */
- public void startServer() {
- //If there are other request methods, such as the following line of code
- 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, "Come in, haha" );
- String uri = request.getPath();
- //This is where you get the header parameters, so be sure to keep this in mind
- Multimap headers = request.getHeaders().getMultiMap();
-
- if (checkUri(uri)) {// is for interface processing
- //Note: This is where you get the parameters for the post request, so be sure to keep it in mind
- Multimap parms = (( AsyncHttpRequestBody<Multimap>)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( "Unable to obtain request address" );
- }
-
- if ( "OPTIONS" .toLowerCase().equals(request.getMethod().toLowerCase())) {
- LogUtil.d(TAG, "OPTIONS exploratory request" );
- addCORSHeaders(Status.REQUEST_OK, response);
- return ;
- }
-
- switch (uri) {
- case "/test" : {//Interface 2
- //This method includes encapsulating the returned interface request data and handling exceptions and cross-domain
- return getXXX(parms);
- }
- default : {
- return addHeaderResponse(Status.REQUEST_ERROR_API);
- }
- }
- } else {
- // Targets the processing of static resources
- String filePath = getFilePath(uri); // Get the file path based on the url
-
- if (filePath == null ) {
- LogUtil.d(TAG, "sd card not found" );
- response.send( "sd card not found" );
- return ;
- }
- File file = new File(filePath);
-
- if (file != null && file.exists()) {
- Log.d(TAG, "file path = " + file.getAbsolutePath());
-
- response.sendFile(file); //Different from nanohttpd
-
- } else {
- Log.d(TAG, "file path = " + file.getAbsolutePath() + " resource does not exist" );
- }
- }
- }
- }
Based on the above example, the following points are mainly mentioned: {It is probably about the usage of API} 1) For example: server.addAction("OPTIONS", "[\d\D]", this) is a general method for filtering requests. The first parameter is the request method, such as "OPTIONS", "DELETE", "POST", "GET", etc. (note that they are in uppercase), the second parameter is the regular expression for filtering URIs, here all URIs are filtered, and the third is the callback parameter. server.post("[\d\D]", this), server.get("[\d\D]*", this) are special cases of the previous method. server.listen(PORT_LISTEN_DEFALT) is the listening port; 2) request.getHeaders().getMultiMap() This is where you get the header parameters, so be sure to keep it in mind; 3) ((AsyncHttpRequestBody<Multimap>)request.getBody()).get() is where the parameters of the post request are obtained; 4) The code for obtaining static resources is in the else of the callback method onResponse, as shown in the example above. 5) Let me talk about the pitfalls of OPTIONS. Because there are only two kinds of http status codes encapsulated in the AndroidAsync framework, if the filtering method does not include a request method such as OPTIONS, the actual http status code returned to the client is 400, and the error message reflected in the browser is actually a cross-domain problem. It took me a long time to find it, so please pay attention. Summarize: 1) The same page: NanoHttpd time: 1.4s AndroidAsync time: 1.4s However, when entering for the second time, the time consumed by AndroidAsync was obviously less than the first time. The author speculates that this is because AndroidAsync does some processing at the bottom layer. 2) From the perspective of API analysis, NanoHttpd is more convenient to use, and the APIs for obtaining the passed parameters are easier to use; AndroidAsync's API is relatively more complicated, such as obtaining params. 3) If you analyze it from the perspective of the scenario, if you need high concurrency, you must use AndroidAsync; but if you don’t need it, then analyze it in detail. |