package net.mengkang.nettyrest; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; import io.netty.handler.codec.http.multipart.InterfaceHttpData; import io.netty.handler.codec.http.multipart.MixedAttribute; import io.netty.util.CharsetUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * convert the netty HttpRequest object to a api protocol object */ public class ApiProtocol { private static final Logger logger = LoggerFactory.getLogger(ApiProtocol.class); private int build = 101; private String version = "1.0"; private String channel = "mengkang.net"; private String geo = null; private String clientIP = null; private String serverIP = null; private String api = null; private String endpoint = null; private String auth = null; private int offset = 0; private int limit = 10; private HttpMethod method = HttpMethod.GET; private Map<String, List<String>> parameters = new HashMap<String, List<String>>(); // get 和 post 的键值对都存储在这里 private String postBody = null; // post 请求时的非键值对内容 public int getBuild() { return build; } public String getVersion() { return version; } public String getChannel() { return channel; } public String getGeo() { return geo; } public String getClientIP() { return clientIP; } public String getServerIP() { return serverIP; } public String getApi() { return api; } public String getEndpoint() { return endpoint; } public String getAuth() { return auth; } public int getOffset() { return offset; } public int getLimit() { return limit; } public HttpMethod getMethod() { return method; } public Map<String, List<String>> getParameters() { return parameters; } private void addParameter(String key, String param) { List<String> params = new ArrayList<>(); params.add(param); this.parameters.put(key, params); } public String getPostBody() { return postBody; } /** * api protocol object initializer * * @param ctx * @param msg * @return */ public ApiProtocol(ChannelHandlerContext ctx, Object msg) { HttpRequest req = (HttpRequest) msg; String uri = req.uri(); if (uri.length() <= 0) { return; } logger.info(uri); this.method = req.method(); parseEndpoint(uri); setIp(ctx, req); queryStringHandler(uri); requestParametersHandler(req); requestBodyHandler(msg); if (this.parameters.size() > 0) { setFields(); } } /** * parse endpoint * match api name and api regex and add resource params into {@link #parameters} * * @param uri */ private void parseEndpoint(String uri) { String endpoint = uri.split("\\?")[0]; if (endpoint.endsWith("/")) { endpoint = endpoint.substring(0, endpoint.length()); } this.endpoint = endpoint; Set<Map.Entry<String, Api>> set = ApiRoute.apiMap.entrySet(); for (Map.Entry<String, Api> entry : set) { Api api = entry.getValue(); Pattern pattern = Pattern.compile("^" + api.getRegex() + "$"); Matcher matcher = pattern.matcher(endpoint); if (matcher.find()) { this.api = api.getName(); if (matcher.groupCount() > 0) { for (int i = 0; i < matcher.groupCount(); i++) { addParameter(api.getParameterNames().get(i), matcher.group(i + 1)); } } break; } } } private void setIp(ChannelHandlerContext ctx, HttpRequest req) { String clientIP = (String) req.headers().get("X-Forwarded-For"); if (clientIP == null) { InetSocketAddress remoteSocket = (InetSocketAddress) ctx.channel().remoteAddress(); clientIP = remoteSocket.getAddress().getHostAddress(); } this.clientIP = clientIP; InetSocketAddress serverSocket = (InetSocketAddress) ctx.channel().localAddress(); this.serverIP = serverSocket.getAddress().getHostAddress(); } /** * set this class field's value by {@link #parameters} * <p> * the code improved log in my blog <a href="http://mengkang.net/614.html">http://mengkang.net/614.html</> */ private void setFields() { Field[] fields = this.getClass().getDeclaredFields(); for (int i = 0, length = fields.length; i < length; i++) { Field field = fields[i]; String fieldName = field.getName(); if (fieldName.equals("logger") || fieldName.equals("method") || fieldName.equals("parameters") || fieldName.equals("postBody")) { continue; } if (!this.parameters.containsKey(fieldName)) { continue; } Class fieldType = field.getType(); field.setAccessible(true); try { if (fieldType == int.class) { field.set(this, Integer.parseInt(this.parameters.get(fieldName).get(0))); } else { field.set(this, this.parameters.get(fieldName).get(0)); } } catch (NumberFormatException | IllegalAccessException e) { logger.error("field set error", e); } this.parameters.remove(fieldName); } } /** * queryString encode, put all the query key value in {@link #parameters} * <p> * use netty's {@link QueryStringDecoder} replace my bad encode method <a href="http://mengkang.net/587.html">http://mengkang.net/587.html</a> * * @param uri */ private void queryStringHandler(String uri) { QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri); if (queryStringDecoder.parameters().size() > 0) { this.parameters.putAll(queryStringDecoder.parameters()); } } /** * request parameters put in {@link #parameters} * * @param req */ private void requestParametersHandler(HttpRequest req) { if (req.method().equals(HttpMethod.POST)) { HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req); try { List<InterfaceHttpData> postList = decoder.getBodyHttpDatas(); for (InterfaceHttpData data : postList) { List<String> values = new ArrayList<String>(); MixedAttribute value = (MixedAttribute) data; value.setCharset(CharsetUtil.UTF_8); values.add(value.getValue()); this.parameters.put(data.getName(), values); } } catch (Exception e) { logger.error(e.getMessage()); } } } /** * request body put in {@link #postBody} * * @param msg */ private void requestBodyHandler(Object msg) { if (msg instanceof HttpContent) { HttpContent httpContent = (HttpContent) msg; ByteBuf content = httpContent.content(); StringBuilder buf = new StringBuilder(); buf.append(content.toString(CharsetUtil.UTF_8)); this.postBody = buf.toString(); } } }