package core.framework.impl.web.request; import core.framework.api.http.ContentType; import core.framework.api.http.HTTPMethod; import core.framework.api.log.Markers; import core.framework.api.util.Files; import core.framework.api.util.Strings; import core.framework.api.web.MultipartFile; import core.framework.api.web.exception.MethodNotAllowedException; import core.framework.impl.log.ActionLog; import core.framework.impl.log.LogParam; import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.form.FormData; import io.undertow.server.handlers.form.FormDataParser; import io.undertow.util.HeaderMap; import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.Deque; import java.util.Map; /** * @author neo */ public final class RequestParser { private final Logger logger = LoggerFactory.getLogger(RequestParser.class); public void parse(RequestImpl request, HttpServerExchange exchange, ActionLog actionLog) throws Throwable { HeaderMap headers = exchange.getRequestHeaders(); String xForwardedFor = headers.getFirst(Headers.X_FORWARDED_FOR); String remoteAddress = exchange.getSourceAddress().getAddress().getHostAddress(); logger.debug("[request] remoteAddress={}", remoteAddress); request.clientIP = clientIP(remoteAddress, xForwardedFor); actionLog.context("clientIP", request.clientIP); String xForwardedProto = headers.getFirst(Headers.X_FORWARDED_PROTO); String requestScheme = exchange.getRequestScheme(); logger.debug("[request] requestScheme={}", requestScheme); request.scheme = xForwardedProto != null ? xForwardedProto : requestScheme; String xForwardedPort = headers.getFirst(Headers.X_FORWARDED_PORT); int hostPort = exchange.getHostPort(); logger.debug("[request] hostPort={}", hostPort); request.port = port(hostPort, xForwardedPort); request.requestURL = requestURL(request, exchange); actionLog.context("requestURL", request.requestURL); for (HeaderValues header : headers) { logger.debug("[request:header] {}={}", header.getHeaderName(), header.toArray()); } logger.debug("[request] path={}", request.path()); String userAgent = headers.getFirst(Headers.USER_AGENT); if (userAgent != null) actionLog.context("userAgent", userAgent); request.method = httpMethod(exchange.getRequestMethod().toString()); actionLog.context("method", request.method()); parseQueryParams(request, exchange); if (request.method == HTTPMethod.POST || request.method == HTTPMethod.PUT) { String contentType = headers.getFirst(Headers.CONTENT_TYPE); request.contentType = contentType == null ? null : ContentType.parse(contentType); parseBody(request, exchange); } } private void parseQueryParams(RequestImpl request, HttpServerExchange exchange) { for (Map.Entry<String, Deque<String>> entry : exchange.getQueryParameters().entrySet()) { String name = decodeQueryParam(entry.getKey()); String value = decodeQueryParam(entry.getValue().getFirst()); logger.debug("[request:query] {}={}", name, value); request.queryParams.put(name, value); } } private String decodeQueryParam(String value) { try { return URLDecoder.decode(value, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new Error(e); } } HTTPMethod httpMethod(String method) { try { return HTTPMethod.valueOf(method); } catch (IllegalArgumentException e) { throw new MethodNotAllowedException("method is not allowed, method=" + method, e); } } private void parseBody(RequestImpl request, HttpServerExchange exchange) throws Throwable { RequestBodyReader.RequestBody body = exchange.getAttachment(RequestBodyReader.REQUEST_BODY); if (body != null) { if (request.contentType == null) return; // pass if post empty body without content type if (ContentType.APPLICATION_JSON.mediaType().equals(request.contentType.mediaType())) { request.body = body.body(); logger.debug("[request] body={}", LogParam.of(request.body)); } else { logger.warn(Markers.errorCode("UNSUPPORTED_CONTENT_TYPE"), "unsupported content type, contentType={}", request.contentType); } exchange.removeAttachment(RequestBodyReader.REQUEST_BODY); } else { parseForm(request, exchange); } } private void parseForm(RequestImpl request, HttpServerExchange exchange) { FormData formData = exchange.getAttachment(FormDataParser.FORM_DATA); if (formData == null) return; for (String name : formData) { FormData.FormValue value = formData.getFirst(name); if (value.isFile()) { if (!Strings.isEmpty(value.getFileName())) { // browser passes empty file name if not choose file in form logger.debug("[request:file] {}={}, size={}", name, value.getFileName(), Files.size(value.getPath())); request.files.put(name, new MultipartFile(value.getPath(), value.getFileName(), value.getHeaders().getFirst(Headers.CONTENT_TYPE))); } } else { logger.debug("[request:form] {}={}", name, value.getValue()); request.formParams.put(name, value.getValue()); } } } String clientIP(String remoteAddress, String xForwardedFor) { if (Strings.isEmpty(xForwardedFor)) return remoteAddress; int index = xForwardedFor.indexOf(','); if (index > 0) return xForwardedFor.substring(0, index); return xForwardedFor; } int port(int hostPort, String xForwardedPort) { if (xForwardedPort != null) { int index = xForwardedPort.indexOf(','); if (index > 0) return Integer.parseInt(xForwardedPort.substring(0, index)); else return Integer.parseInt(xForwardedPort); } return hostPort; } private String requestURL(RequestImpl request, HttpServerExchange exchange) { StringBuilder builder = new StringBuilder(); if (exchange.isHostIncludedInRequestURI()) { // GET can use absolute url as request uri, http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html builder.append(exchange.getRequestURI()); } else { String scheme = request.scheme; int port = request.port; builder.append(scheme) .append("://") .append(exchange.getHostName()); if (!(("http".equals(scheme) && port == 80) || ("https".equals(scheme) && port == 443))) { builder.append(':').append(port); } builder.append(exchange.getRequestURI()); } String queryString = exchange.getQueryString(); if (!Strings.isEmpty(queryString)) builder.append('?').append(queryString); return builder.toString(); } }