package com.web2apk;

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.util.Log;

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;

import fi.iki.elonen.NanoHTTPD;

public class LocalServer extends NanoHTTPD {


    public static int localServerPort = 8888;
    public static int timeOut = 5000;
    public static String webAssetsRoot = "web";


    private LocalServer(int port) {
        super(port);
    }
    private static LocalServer me;
    private static Context ctx;

    public static final LocalServer create(Context context){
        ctx = context;
        if(me != null){
            return  me;
        }
        me = new LocalServer(localServerPort);
        return me;
    }


    public static final void exit(){
        if(me == null){
            return;
        }
        me.stop();
        me = null;
        Log.e("====>","LocalServer exited.");
    }

    @Override
    public Response serve(IHTTPSession session) {
        String uri = session.getUri();
        return  defaultRespond(session,uri);
    }


    protected Response getForbiddenResponse(String s) {
        return newFixedLengthResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: " + s);
    }

    protected Response getInternalErrorResponse(String s) {
        return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "INTERNAL ERROR: " + s);
    }

    protected Response getNotFoundResponse() {
        return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Error 404, file not found.");
    }

    private Response assetsFileResponse(String filePath, String mime) {
        Response res;
        AssetManager am = ctx.getResources().getAssets();
        filePath = webAssetsRoot+filePath;
        try {
            AssetFileDescriptor afd = am.openFd(filePath);
            FileInputStream fin = afd.createInputStream();
//            Log.e("====>","assetsFileResponse:"+filePath);
            res = newFixedLengthResponse(Response.Status.OK, mime, fin, afd.getLength());
            res.addHeader("Content-Length", "" + afd.getLength());
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("====>","assetsFileResponse ERROR.filePath:"+filePath+" e:"+e);
//            return getInternalErrorResponse("read file");
            return getNotFoundResponse();
        }

        return res;
    }

    //
    private Response crossOutUrl(IHTTPSession session, String uri){
        String method = session.getMethod().toString();
        Log.i("====>","crossOutUrl:" + uri+","+method);
        Response res;
        try {
            Map<String, List<String>> paras = session.getParameters();
            uri = (paras.get("u")).get(0);
            Log.i("====>","new uri:[" + uri+"]");

            Map<String, String> headers = session.getHeaders();


            URL url = new URL(uri);
            URLConnection connection = url.openConnection();
            HttpURLConnection http =  (HttpURLConnection) connection;
            http.setRequestMethod(method);
            headers.remove("referer");
            headers.remove("host");
            headers.remove("remote-addr");
            headers.remove("origin");
            for (Map.Entry<String, String> entry : headers.entrySet()) {
//                Log.i("====>","HEADER:" + entry.getKey() + "=" + entry.getValue());
                http.setRequestProperty(entry.getKey(),entry.getValue());
            }
            http.setReadTimeout(timeOut);
//          http.setConnectTimeout(5000);
//          http.setDoInput(true);//default is true, don't need
            if(method.equals("POST")){
                http.setDoOutput(true);
                http.setUseCaches(false);
                OutputStream outputStream = http.getOutputStream();
                int maxLen = session.getInputStream().available();
                int bufLen = (maxLen>= 0 && maxLen < 1024) ? maxLen : 1024;
                byte[] bys = new byte[bufLen];
//                Log.e("====>","maxLen::"+maxLen+",bufLen:"+bufLen);
                int i = 0;
                for (int j = 0; j < maxLen; j++) {
                    if (i >= bufLen){
                        outputStream.write(bys,0,i);
//                        Log.e("====>","write bys1:"+new String(bys)+",i:"+i);
                        i = 0;
                    }
                    byte bt = (byte)session.getInputStream().read();
                    bys[i] = (byte)bt;
                    i++;
                }
                if(i>0){
                    outputStream.write(bys,0,i);
//                    Log.e("====>","write bys2:"+new String(bys)+",i:"+i);
                }
                outputStream.flush();
//                Log.e("====>","write done!!!!");
                outputStream.close();
            }
            InputStream inputStream = http.getInputStream();
            final int httpResStatus = http.getResponseCode();
            res = newChunkedResponse(Response.Status.lookup(httpResStatus),getMimeTypeForFile(uri),inputStream);

        } catch (Exception e) {
            Log.e("====>","ERR-crossOutUrl:"+method+":" + uri);
            e.printStackTrace();
            return getInternalErrorResponse("cross uri:"+uri);
        }
        return res;
    }


    private Response defaultRespond(IHTTPSession session, String uri) {
        if(this.ctx ==null){
            return getInternalErrorResponse("ctx");
        }
        if (Method.OPTIONS.equals(session.getMethod())) {
            return NanoHTTPD.newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, null, 0);
        }
        if(uri.indexOf("__cross__") > 0){
            return crossOutUrl(session,uri);
        }


        String mimeTypeForFile = getMimeTypeForFile(uri);
        Response response = assetsFileResponse(uri, mimeTypeForFile);

//        Log.e("====>","defaultRespond res done:"+uri);
        return response != null ? response : getNotFoundResponse();
    }




}

This code implements a local web server that can serve files from the assets folder and proxy requests to external URLs. Here's a breakdown:

  • LocalServer Class: This class extends NanoHTTPD to create a simple web server. It initializes the port, timeout, and the root path for assets (webAssetsRoot).
  • create() Method: This static method initializes the server and stores the context. It ensures that only one instance of the server is running.
  • exit() Method: Stops the server and releases the instance.
  • serve() Method: Handles incoming requests by calling defaultRespond.
  • defaultRespond() Method: This method handles the core logic of the server. It checks the request method and URI. If the request is an OPTIONS request, it returns a simple OK response. If the URI contains __cross__, it triggers the cross-domain request handling (crossOutUrl). Otherwise, it serves the requested file from the assets folder.
  • assetsFileResponse() Method: This method serves a file from the assets folder with the specified mime type. If the file is not found, it returns a 404 response.
  • crossOutUrl() Method: This method handles cross-domain requests. It extracts the target URL from the request parameters, sets up the request headers, and sends the request to the target server. It then returns a response based on the target server's response.

Key Features:

  • Serving local assets: This allows you to host your web app's files locally and test them directly on the device.
  • Cross-domain request handling: This allows you to make requests to external servers from your local web app, overcoming CORS restrictions.

How to Use:

  1. Initialize the server: Call LocalServer.create(context) to create an instance of the server.
  2. Start the server: Call me.start() to start the server.
  3. Access the server: You can access the server from your web app using http://localhost:8888.
  4. Stop the server: Call LocalServer.exit() to stop the server.

Possible Enhancements:

  • Error handling: Add more robust error handling and logging.
  • Security: Implement security measures like SSL/TLS for sensitive requests.
  • Configuration: Allow customizing server settings like port and timeout via settings.
  • Additional features: Support for other HTTP methods like PUT, DELETE, etc.

This code provides a basic foundation for building a local web server for your Web2Apk development. You can customize and extend it based on your specific requirements.

Android Local Web Server for Web2Apk: Serve Assets and Proxy Requests

原文地址: https://www.cveoy.top/t/topic/mmkx 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录