安卓demo,使用NanoHTTPD; 使用未被占用的端口,前端资源转发内部文件夹,且限制其他应用访问
为了在Android中使用NanoHTTPD创建一个HTTP服务器,监听未被占用的端口,并将前端资源转发到内部文件夹,同时限制其他应用访问,可以参考以下示例代码:
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Random;
import fi.iki.elonen.NanoHTTPD;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_PERMISSIONS = 100;
private static final String TAG = "MainActivity";
private static final String INTERNAL_FOLDER_PATH = "/data/data/<your_package_name>/files/public_html"; // 替换为你的内部文件夹路径
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.INTERNET},
REQUEST_PERMISSIONS);
} else {
startServer();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_PERMISSIONS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED &&
grantResults[1] == PackageManager.PERMISSION_GRANTED &&
grantResults[2] == PackageManager.PERMISSION_GRANTED) {
startServer();
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
}
}
}
private void startServer() {
Random rand = new Random();
int port = -1;
while (port == -1 || !isPortAvailable(port)) { // 循环查找可用端口
port = rand.nextInt(60000 - 1024) + 1024; // 生成1024到65535之间的随机端口号
}
MyHTTPServer server = null;
try {
server = new MyHTTPServer(new InetSocketAddress(InetAddress.getLocalHost(), port));
Log.i(TAG, "Server started on port: " + port);
server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
} catch (IOException e) {
Log.e(TAG, "Failed to start server on port: " + port, e);
Toast.makeText(this, "Failed to start server on port: " + port, Toast.LENGTH_SHORT).show();
}
}
private boolean isPortAvailable(int port) {
// 检查端口是否可用,这可以通过尝试绑定来实现,但此处简化处理,实际应用中可能需要更复杂的检查逻辑
return true; // 这里假设端口可用,实际应替换为相应的方法进行检测
}
public static class MyHTTPServer extends NanoHTTPD {
public MyHTTPServer(InetSocketAddress address) {
super(address);
}
@Override
public Response serve(IHTTPSession session) {
Method method = session.getMethod();
String uri = session.getUri();
File file = new File(INTERNAL_FOLDER_PATH + uri);
if (file.exists() && file.isFile()) {
try {
FileInputStream fis = new FileInputStream(file);
byte[] data = new byte[(int) file.length()];
fis.read(data);
fis.close();
return newFixedLengthResponse(Response.Status.OK, MIME_TYPES.get("html"), ByteBuffer.wrap(data), data.length);
} catch (Exception e) {
Log.e(TAG, "Error serving file", e);
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Error serving file");
}
} else {
return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "File not found");
}
}
}
}
在这个示例中:
- 首先,我们在启动服务器之前请求了必要的权限。
- 然后,我们寻找一个未被占用的端口并启动服务器。
- 自定义的
MyHTTPServer
类重写了serve()
方法以服务来自客户端的请求。当接收到请求时,它会从指定的内部文件夹加载文件内容作为响应。
注意:
- 要确保你已经正确设置了内部文件夹路径(
INTERNAL_FOLDER_PATH
)。 - 关于“限制其他应用访问”,由于是在同一设备上运行,Android默认的安全模型通常不允许其他应用直接访问此应用的数据目录。然而,如果需要进一步强化安全性,可以通过ContentProvider或其他安全机制提供更加精细的控制。
- 在真实场景下,请务必实现
isPortAvailable()
方法,用来检查端口是否已被占用。
此外,在Android应用中开启HTTP服务器需要注意系统的后台执行策略和网络权限管理,确保服务能在合适的时机和环境下运行。
安卓demo,使用NanoHTTPD; 对 /static 路径转发到应用内部静态文件,/api 转发到远程服务端;使用webview 访问NanoHTTPD
为了实现使用NanoHTTPD在Android中创建一个简单的本地服务器,将 /static
路径映射到应用内部的静态文件夹,并将 /api
转发到远程服务端,同时通过WebView访问这个本地服务器,可以参考以下示例代码:
首先,扩展NanoHTTPD以处理不同的路由请求:
import fi.iki.elonen.NanoHTTPD;
import fi.iki.elonen.router.RouterNanoHTTPD;
public class CustomServer extends RouterNanoHTTPD {
public CustomServer(int port) {
super(port);
setupRoutes();
}
private void setupRoutes() {
// 将/static路径映射到本地静态资源目录
addRoute("/static/*", new StaticFileHandler(getInternalFilesDir("public_html"))); // 替换为你的内部文件夹路径
// 将/api转发到远程服务端
addRoute("/api/*", new ApiProxyHandler()); // 这里假设有个ApiProxyHandler来处理API转发逻辑
}
// 静态文件处理器类
public static class StaticFileHandler extends SimpleWebServer.StaticResourceHandler {
public StaticFileHandler(File rootDir) {
super(rootDir, null, true);
}
}
// API代理处理器类(需要实现)
public static class ApiProxyHandler implements RouterNanoHTTPD.UriResource {
@Override
public NanoHTTPD.Response get(NanoHTTPD.IHTTPSession session) {
// 在这里编写向远程服务器发送请求并返回响应的逻辑
// 示例:
String remoteUrl = "http://your-remote-server.com" + session.getUri(); // 构造远程URL
// 使用网络库(如OkHttp)发起请求,获取响应,并将其转换为NanoHTTPD的Response对象返回
return NanoHTTPD.newFixedLengthResponse(Response.Status.OK, "application/json", "API Response"); // 示例响应
}
// 可能还需要覆盖其他方法以支持POST、PUT等HTTP方法
}
// 启动服务器
public static void startServer(Context context) {
int port = 8080; // 设置你的端口号
CustomServer server = new CustomServer(port);
try {
server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
Log.i("CustomServer", "Server started on port: " + port);
} catch (IOException e) {
Log.e("CustomServer", "Failed to start server on port: " + port, e);
}
}
}
然后,在你的Activity中启动服务器并设置WebView:
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_PERMISSIONS = 100;
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.web_view);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.INTERNET},
REQUEST_PERMISSIONS);
} else {
startServerAndLoadWebView();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_PERMISSIONS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED &&
grantResults[1] == PackageManager.PERMISSION_GRANTED &&
grantResults[2] == PackageManager.PERMISSION_GRANTED) {
startServerAndLoadWebView();
} else {
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
}
}
}
private void startServerAndLoadWebView() {
CustomServer.startServer(this); // 启动服务器
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAllowFileAccessFromFileURLs(true);
settings.setAllowUniversalAccessFromFileURLs(true);
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 确保WebView加载本地服务器资源而不是跳转外部浏览器
if (url.startsWith("http://localhost:8080")) { // 注意替换为你服务器的实际地址和端口
view.loadUrl(url);
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
});
// 加载本地服务器上的HTML文件作为起始页面
webView.loadUrl("http://localhost:8080/index.html"); // 替换为你服务器上实际的HTML文件路径
}
}
注意:
- 在上述示例中,我们使用了
RouterNanoHTTPD
以便更方便地处理路由。 ApiProxyHandler
类需要实现向远程服务器转发请求并返回响应的功能。你可以选择任何适合的网络库(例如OkHttp)来完成这一部分。- 在实际项目中,请确保已经正确设置了内部文件夹路径和远程服务器URL。
- 对于安全性和性能,请根据实际情况调整配置,如开启HTTPS、限制允许的请求类型等。
安卓使用 NanoHTTPD 或RouterNanoHTTPD 开启https服务及转发https请求到远程
在Android中,NanoHTTPD并不直接支持HTTPS服务。但是,你可以通过集成SSL/TLS证书和自定义SocketFactory来实现HTTPS功能。然而,RouterNanoHTTPD本身并没有提供内置的HTTPS支持,你需要对NanoHTTPD进行扩展以支持HTTPS。
以下是一个基本的示例,展示如何使用自定义的SecureServerSocketFactory来创建一个支持HTTPS的服务器(基于NanoHTTPD)
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import fi.iki.elonen.NanoHTTPD;
import fi.iki.elonen.router.RouterNanoHTTPD;
public class SecureCustomServer extends RouterNanoHTTPD {
private static final String SSL_KEYSTORE_PATH = "path_to_your_keystore";
private static final String SSL_KEYSTORE_PASSWORD = "your_keystore_password";
private static final int PORT = 4443; // HTTPS通常使用非标准端口
public SecureCustomServer() {
super(PORT);
setupSSL();
setupRoutes();
}
private void setupSSL() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream keyInput = new FileInputStream(SSL_KEYSTORE_PATH);
try {
keyStore.load(keyInput, SSL_KEYSTORE_PASSWORD.toCharArray());
} finally {
keyInput.close();
}
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, SSL_KEYSTORE_PASSWORD.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
this.myServerSocketFactory = sslSocketFactory;
} catch (Exception e) {
throw new RuntimeException("Failed to initialize SSL", e);
}
}
private void setupRoutes() {
addRoute("/api/*", new ApiProxyHandler()); // 假设有个ApiProxyHandler来处理API转发逻辑
}
// ... 其他代码如前所述 ...
}
对于将HTTPS请求转发到远程服务端的部分,同样需要在网络请求库(例如OkHttp或Apache HttpClient等)中配置相应的SSL上下文,并正确发起HTTPS请求。下面是一个简化的OkHttp示例:
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class ApiProxyHandler implements RouterNanoHTTPD.UriResource {
private OkHttpClient httpClient;
public ApiProxyHandler() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
// 如果需要,可以在这里添加信任的证书、客户端证书等配置
httpClient = builder.build();
}
@Override
public NanoHTTPD.Response get(RouterNanoHTTPD.UriResource uriResource, Map<String, String> urlParams, NanoHTTPD.IHTTPSession session) {
String remoteUrl = "https://your-remote-server.com/api" + session.getUri(); // 构造远程URL
Request request = new Request.Builder()
.url(remoteUrl)
.build();
try (Response response = httpClient.newCall(request).execute()) {
return NanoHTTPD.newFixedLengthResponse(
Response.Status.valueOf(response.code()),
response.header("Content-Type"),
response.body().string());
} catch (IOException e) {
Log.e("ApiProxyHandler", "Error forwarding API call", e);
return NanoHTTPD.newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error");
}
}
// 可能还需要覆盖其他方法以支持POST、PUT等HTTP方法
}
请确保替换上述示例中的路径、密码以及远程服务器地址,并根据实际情况调整证书加载方式。同时,请注意在Android环境中运行HTTPS服务器可能需要额外处理权限和安全问题。