Establishment of NanoHttpd Android HTTP Sever

Keywords: Mobile Android Session network

Original articles, reprinted please indicate the source: Establishment of NanoHttpd Android HTTP Sever:http://www.jianshu.com/p/ef6279a429d4

This article originated from the brief book, CSDN is an excellent platform, so it also appeared in CSDN.

Preface

Recently, I received a task to build an HTTP Server on my mobile phone for file sharing. That is, after the application on the mobile phone is started, the computer can connect to the mobile phone through the ip address of the mobile phone in the local area network, and download the files shared by the mobile phone. The function is very convenient, and because of the relationship between http server, it should be possible to connect multiple devices to the same mobile phone to download files. At the same time, the technology is realized by wifi of LAN or hotspot of mobile phone, which makes the transmission efficiency very high. (Suddenly, a function should be added after that, so that Android mobile phone without the software can use mobile browser to connect the HTTP server to download the software.) Because it is a practice first, and it can also understand the process of establishing HTTP server first, to http protocol, H. The interaction between TTP server and client is analyzed, so data binding and other design patterns are not used to avoid the irrelevant factors interfering with the development process of the program.

HTTP Protocol

The content of TCP/IP protocol needs no more words, and readers can understand it by themselves. http is the standard for requests and responses between clients and servers. Servers process different http requests sent by clients and send reply messages to clients after processing. It transfers all kinds of data based on TCP/IP protocol.
In the process of writing http server, you may wonder that HTTP is just a protocol. If we use a custom HTTP server (maybe just called a server) to transfer data, and use a custom http client (similarly, just call a client) to interact with the client and the server as well as to transfer data, we don't really need to make HTTP a protocol. With http protocol, we can write an Android program, use socket and server socket programs, run the program with different devices, and use our own identifiers to achieve interaction and transmission. Think about it carefully, does our device have to install this program to run and use, and for pc, it is impossible to install the Android program, so the data transmission we want to achieve can not be achieved? Think about it this way, we should use mature, widely accepted and available protocols to implement applications, so that our applications are more universal and compatible with different devices.
There are some deviations. Now let's go back to the content of the http protocol.
http protocol has the following characteristics:
1. Connectionless: disconnect after processing the request and getting a response
2. Media independence: any type of data can be accomplished through http protocol
3. Statelessness: Does not record previous status information

From these characteristics, we can see that http protocol has the characteristics of fast transmission speed, fast response speed and convenient transmission.
Next, let's look at the format of http client sending requests:

The description on the graph is clear enough, and the format in which the server sends the response:

The most common requests in http protocol are get and post, so our http server basically implements these two methods to upload and download files. The get request is usually used to request the specified page information and return the entity body. Post requests are used to submit forms or upload files. Other request methods are detailed on the network. http's corresponding headers and status codes have more detailed content on the content network, which will not be repeated here.

http server

Since we want to build an http server in Android, we can use java to build it. The basic idea is to build a server socket in Java to wait for the connection. After the connection, the server continuously receives the data from the client and processes it. Because of the http protocol, the content of the http protocol is also needed for data processing and reply. It is difficult to build an http server from scratch. To simplify the processing, we first use nanohttpd to build the server.

preparation in advance

  1. First on the official website nanohttpd Download to the local, decompress and enter the folder, using mvn compile and man package (my computer is Linux) will automatically compile and build jar files, jar files in the core folder target folder.
  2. New projects in android studio introduce jar packages into the dependency relationship of projects. The introduced jar packages can be used to build http server. Basically, it is to build a new class, which inherits from NanoHTTPD.

Official Start

Main interface

The layout of the main interface is simple, just describe it briefly, and no code is pasted. The main interface uses a TextView, which is used to display the ip address of the host and the port number of the http server after starting the application. Other hosts can use the ip address and port number to connect remotely.

    //Get IP address
    public static String getLocalIpStr(Context context){
        WifiManager wifiManager=(WifiManager)context.getSystemService(Context.WIFI_SERVICE);
        WifiInfo wifiInfo=wifiManager.getConnectionInfo();
        return  intToIpAddr(wifiInfo.getIpAddress());
    }

    private static String intToIpAddr(int ip){
        return (ip & 0xFF)+"."
                + ((ip>>8)&0xFF) + "."
                + ((ip>>16)&0xFF) + "."
                + ((ip>>24)&0xFF);
    }

HTTP Server

Here we begin the formal construction of http server. First, we build a server class inherited from NanoHTTPD, and implement the basic methods in NanoHTTPD to complete the operation we want.

public class FileServer extends NanoHTTPD{
    public static final int DEFAULT_SERVER_PORT= com.example.zjt.nanohttpexample.Status.MY_PORT;//For 8080
    public static final String TAG = FileServer.class.getSimpleName();
    //root directory
    private static  final String REQUEST_ROOT = "/";
    private List<SharedFile> fileList;//List of files to share

    public FileServer(List<SharedFile> fileList){
        super(DEFAULT_SERVER_PORT);
        this.fileList = fileList;
    }
    //This method is called when a connection is accepted
    public Response serve(IHTTPSession session){
        if(REQUEST_ROOT.equals(session.getUri())||session.getUri().equals("")){
            return responseRootPage(session);
        }
        return responseFile(session);
    }
    //For requesting the root directory, return the list of shared files
    public Response responseRootPage(IHTTPSession session){
        StringBuilder builder = new StringBuilder();
        builder.append("<!DOCTYPER html><html><body>");
        builder.append("<ol>");
        for(int i = 0 , len = fileList.size(); i < len ; i++){
            File file = new File(fileList.get(i).getPath());
            if(file.exists()){
                 //The links between files and downloaded files define a file class. Here, the getPath method is used to get the path and the getName method is used to get the file name.
                builder.append("<li> <a href=\""+file.getPath()+"\">"+file.getName()+"</a></li>");
            }
        }
        builder.append("<li>Number of shared documents:  "+fileList.size()+"</li>");
        builder.append("</ol>");
        builder.append("</body></html>\n");
        //echo reply 
        return Response.newFixedLengthResponse(String.valueOf(builder));
    }
    //For the request file, return the downloaded file
    public Response responseFile(IHTTPSession session){
        try {
            //uri: A string used to mark a file resource, which is the file path
            String uri = session.getUri();
            //File input stream
            FileInputStream fis = new FileInputStream(uri);
            // Returning to OK and transferring files at the same time, in order to be safe, there should be another processing, that is, to determine whether this file is the file we share, to avoid client access to other personal files?
            return Response.newFixedLengthResponse(Status.OK,"application/octet-stream",fis,fis.available());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return response404(session,null);
    }
    //When a page does not exist or a file does not exist
    public Response response404(IHTTPSession session,String url) {
        StringBuilder builder = new StringBuilder();
        builder.append("<!DOCTYPE html><html>body>");
        builder.append("Sorry,Can't Found" + url + " !");
        builder.append("</body></html>\n");
        return Response.newFixedLengthResponse(builder.toString());
    }
}

It's enough to implement these on the server, and there are some files that you choose to share later.

JFileChooser

File selector is used to select files to share. This place is really a pit. The path chosen at first is wrong. After checking a lot of information, we finally found a reliable implementation on the Internet. The original blogger has forgotten who it is. Thank you.
Code:

//
public class FileChooser extends AppCompatActivity {
    private final int FILE_SELECT_CODE = 1;//Code for file selection
    private Button chooser;//Click on the button to call the system selector to select the file
    private Uri uri = null;
    private ListView fileListView;
    private FileAdapter fileAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.filechooser);
        initView();
        initListener();
    }

    private void initView() {
        chooser = findViewById(R.id.chooser);
        fileListView = findViewById(R.id.filelist);
        fileAdapter = new FileAdapter(Status.fileLists, this);
        fileListView.setAdapter(fileAdapter);
    }

    private void initListener() {
        chooser.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showFileChooser();
            }
        });
    }
    //File selection
    private void showFileChooser() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("*/*");//Any file can be shared 
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        //File selector for calling system
        startActivityForResult(Intent.createChooser(intent, "Please choose the file to share"), FILE_SELECT_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case FILE_SELECT_CODE:
                if (resultCode == RESULT_OK) {
                    uri = data.getData();
                    SharedFile sharedFile = new SharedFile();
                    sharedFile.setPath(FileUtils.getPath(this,uri));//This place is a pit. It's wrong to choose the file path directly on the 7.0 and 7.1 mobile phones.
                    sharedFile.setFilename(path2Name(FileUtils.getPath(this,uri)));
                    sharedFile.setSharedtime(getTime());
                    Status.fileLists.add(sharedFile); //The list of shared files is global, so the file selector can add files to it and the server can read file information from it.
                    fileAdapter.notifyDataSetChanged();
                }
                break;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    //Converting the full path of a file to a file name
    private String path2Name(String path){
        String name=null;
        int len=path.length();
        if(path.charAt(len-1)=='/'){
            int a=-1,b=0;
            while((a=path.indexOf('/',b+1))>0){
                if(a==len-1)break;
                b=a;
            }
            name = path.substring(b+1,len);
        }else {
            int a=-1,b=0;
            while((a=path.indexOf('/',b+1))>0){
                if(a==len-1)break;
                b=a;
            }
            name = path.substring(b+1,len);
        }
        return name;
    }
   //Time to share files
    private String getTime(){
        Date date = new Date();
        return date.toString();
    }
}

File Tool Class:

public class FileUtils {
    /**
     * Return the local absolute path of the file
     *
     * @param context
     * @param uri
     * @return path of the selected image file from gallery
     */
    @SuppressLint("NewApi")
    public static String getPath(final Context context, final Uri uri) {
    //File local absolute path
        // check here to KITKAT or new version
        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {

            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/"
                            + split[1];
                }
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"),
                        Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] { split[1] };

                return getDataColumn(context, contentUri, selection,
                        selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {

            // Return the remote address
            if (isGooglePhotosUri(uri))
                return uri.getLastPathSegment();

            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context
     *            The context.
     * @param uri
     *            The Uri to query.
     * @param selection
     *            (Optional) Filter used in the query.
     * @param selectionArgs
     *            (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri,
                                       String selection, String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = { column };

        try {
            cursor = context.getContentResolver().query(uri, projection,
                    selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri
                .getAuthority());
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri
                .getAuthority());
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri
                .getAuthority());
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    public static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri
                .getAuthority());
    }
}

complimentary close

Through the above, we have completed a simple mobile server, which simply realizes file sharing. Other devices can connect to the mobile server and download the shared files using browsers. Of course, there are many places to modify and expand, but from the point of view of establishing an http server, the goal has been achieved. .

Posted by dacio on Fri, 07 Jun 2019 19:29:50 -0700