如何扩展NanoHttpd实现以处理NOTIFY/SUBSCRIBE等自定义HTTP方法
解决NanoHttpd支持NOTIFY/SUBSCRIBE自定义HTTP方法的问题
我之前在基于NanoHttpd开发插件时也碰到过一模一样的问题,你的报错原因很明确:NanoHttpd在解析请求的阶段会校验HTTP方法是否属于它内置的Method枚举,而NOTIFY、SUBSCRIBE不在默认枚举列表里,所以请求还没走到你的serve()方法就被拦截返回BAD REQUEST了。而且你写的notify()方法根本不会被调用,因为NanoHttpd只会自动调用与内置Method枚举对应的方法(比如get()对应GET、post()对应POST)。
下面是我亲测有效的完整解决方案:
步骤1:自定义HTTPSession绕过方法校验
我们需要重写NanoHttpd的请求解析逻辑,让它允许自定义方法。创建一个继承自HTTPSession的自定义类,修改方法解析逻辑:
import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.NanoHTTPD.HTTPSession; import fi.iki.elonen.NanoHTTPD.Method; import java.lang.reflect.Field; import java.net.Socket; public class CustomHttpSession extends HTTPSession { private String actualRequestMethod; public CustomHttpSession(Socket socket, int timeout) { super(socket, timeout); } @Override protected boolean parseRequest() { try { // 读取请求行的第一行 String requestLine = readLine(); if (requestLine == null) { return false; } // 拆分请求行:方法、URI、协议 String[] requestParts = requestLine.split(" ", 3); if (requestParts.length != 3) { return false; } // 保存真实的请求方法(包括NOTIFY/SUBSCRIBE) actualRequestMethod = requestParts[0]; // 尝试解析为内置Method,失败则用GET作为占位符(避免父类校验失败) Method parsedMethod; try { parsedMethod = Method.valueOf(actualRequestMethod); } catch (IllegalArgumentException e) { parsedMethod = Method.GET; } // 通过反射修改父类的private字段method,绕过校验 Field methodField = HTTPSession.class.getDeclaredField("method"); methodField.setAccessible(true); methodField.set(this, parsedMethod); // 继续完成URI和协议的设置 setUri(requestParts[1]); setProtocol(requestParts[2]); // 解析请求头和请求体,沿用父类逻辑 return parseHeaders() && parseBody(); } catch (Exception e) { e.printStackTrace(); return false; } } // 对外暴露真实的请求方法 public String getActualRequestMethod() { return actualRequestMethod; } }
步骤2:重写NanoHttpd的createSession方法
在你的NanoHttpd子类中,重写createSession方法,返回我们自定义的会话类:
@Override protected HTTPSession createSession(Socket socket) { return new CustomHttpSession(socket, getTimeout()); }
步骤3:在serve方法中处理自定义方法
现在请求能正常走到你的serve()方法了,你需要在这里获取真实的请求方法,然后分发处理(或者传给Cordova前端):
修改你的serve()方法,先获取真实方法:
@Override public Response serve(IHTTPSession session) { Log.d(this.getClass().getName(), "New request is incoming!"); String requestUUID = UUID.randomUUID().toString(); PluginResult pluginResult = null; // 获取真实的请求方法(兼容自定义方法) String requestMethod = session instanceof CustomHttpSession ? ((CustomHttpSession) session).getActualRequestMethod() : session.getMethod().name(); try { // 在createJSONRequest中把requestMethod也加入到JSON里,让前端知道是NOTIFY/SUBSCRIBE请求 pluginResult = new PluginResult( PluginResult.Status.OK, this.createJSONRequest(requestUUID, session, requestMethod)); } catch (JSONException e) { e.printStackTrace(); } pluginResult.setKeepCallback(true); this.webserver.onRequestCallbackContext.sendPluginResult(pluginResult); // 等待前端响应的逻辑保持不变 while (!this.webserver.responses.containsKey(requestUUID)) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } JSONObject responseObject = (JSONObject) this.webserver.responses.get(requestUUID); Log.d(this.getClass().getName(), "responseObject: " + responseObject.toString()); Response response = null; try { response = newFixedLengthResponse( Response.Status.lookup(responseObject.getInt("status")), getContentType(responseObject), responseObject.getString("body") ); Iterator<?> keys = responseObject.getJSONObject("headers").keys(); while (keys.hasNext()) { String key = (String) keys.next(); response.addHeader( key, responseObject.getJSONObject("headers").getString(key) ); } } catch (JSONException e) { e.printStackTrace(); } return response; }
同时,修改你的createJSONRequest方法,把requestMethod作为字段加入到返回的JSON中,这样Cordova前端就能识别并处理NOTIFY/SUBSCRIBE请求了。
额外说明
- 如果不需要把请求传给前端处理,而是直接在Java层处理NOTIFY/SUBSCRIBE,那可以在
serve()方法里直接判断:
然后实现if ("NOTIFY".equalsIgnoreCase(requestMethod)) { return handleNotify(session); } else if ("SUBSCRIBE".equalsIgnoreCase(requestMethod)) { return handleSubscribe(session); }handleNotify和handleSubscribe方法即可。 - 反射修改父类字段的方式是因为NanoHttpd的
method字段是private的,如果你的NanoHttpd版本有公开的方法设置method,那可以不用反射。
内容的提问来源于stack exchange,提问作者arjun
相关产品推荐
相关产品推荐

