GeoServer之Dispatcher类


Dispatches an http request to an open web service (OWS). 向OWS开放网络服务转发(派发)http网络请求

An OWS request contains three bits of information: 一个OWS请求包含三点信息:

  1. The service being called被叫的服务
  2. The operation of the service to execute执行的服务的操作
  3. The version of the service ( optional )服务版本(可选)

Additional, an OWS request can contain an arbitray number of additional parameters. 另外,一个OWS请求可以包含任意数量的附加参数。

An OWS request can be specified in two forms. The first form is known as "KVP" in which all the parameters come in the form of a set of key-value pairs. Commonly this type of request is made in an http "GET" request, the parameters being specified in the query string: 一个OWS请求可以用两种方式指定。第一种方式是KVP,其中所有的参数是以键值对的集合形式出现的。通常, 这种形式的请求是在一个http的“GET”请求中指定的,参数在查询字符串中指定。

 http://www.xyz.com/geoserver?service=someService&request=someRequest&version=X.Y.Z¶m1=...¶m2=...
 

This type of request can also be made in a "POST" request in with a mime-type of "application/x-www-form-urlencoded". 该类型的请求也能在一个“POST”请求中指定,伴随着mime-type为"application/x-www-form-urlencoded"。

The second form is known as "XML" in which all the parameters come in the form of an xml document. This type of request is made in an http "POST" request. 第二种形式称为“XML”,其中所有参数都以XML文件的形式出现。这种类型的请求是在http“POST”请求中发出的。


 <?xml version="1.0" encoding="UTF-8"?>
 
 ...
 ...
 ...
 
 

When a request is received, the service the version parameters are used to locate a service desciptor, an instance of {@link Service} . With the service descriptor, the request parameter is used to locate the operation of the service to call. @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org

当接收到请求时,服务和版本参数来定位服务描述器,{@link service}的实例。使用服务描述器,request参数用于定位要调用的服务的操作。

@作者Justin Deoliveira,开放式规划项目,jdeolive@openplans.org

public class Dispatcher extends AbstractController {

它继承了AbstractController,AbstractController的作用和Controller类似。某些时候我们也可以使用它替代Controller。参考:https://www.xttblog.com/?p=1635

初始化:

    @Override
    protected void initApplicationContext(ApplicationContext context) {//初始化应用上下文
        // load life cycle callbacks加载生命周期回调
        callbacks = GeoServerExtensions.extensions(DispatcherCallback.class, context);

        // setup the xml lookahead value
        String lookahead = GeoServerExtensions.getProperty("XML_LOOKAHEAD", context);
        if (lookahead != null) {
            try {
                int lookaheadValue = Integer.valueOf(lookahead);
                if (lookaheadValue <= 0)
                    logger.log(
                            Level.SEVERE,
                            "Invalid XML_LOOKAHEAD value, "
                                    + "will use "
                                    + XML_LOOKAHEAD
                                    + " instead");
                XML_LOOKAHEAD = lookaheadValue;
            } catch (Exception e) {
                logger.log(
                        Level.SEVERE,
                        "Invalid XML_LOOKAHEAD value, " + "will use " + XML_LOOKAHEAD + " instead");
            }
        }
    }

可见其主要就是为callback参数赋初始值。

init(Request)

该方法将对请求对象做一些处理。

测试1:testReadContextAndPath

测试2:testReadOpContext

从OWS XML请求正文读取以下参数:*服务

测试3:testReadOpPost

一旦确定传入请求是带有请求正文的HTTP POST请求,并且该请求的{@link Request#getInput()输入读取器}已被调用;为了预分析 XML 请求正文根元素并建立以下请求属性:

  • {@link Request#setNamespace namespace}
  • {@link Request#setPostRequestElementName PostRequestElementName}
  • {@link Request#setRequest
  • {@link Request#setService service}
  • {@link Request#setVersion version}
  • {@link Request#setOutputFormat outputFormat}

参数req:基于 xml 请求正文的根元素将属性设置为的请求

返回值return:包含已分析参数的 {@link Map}。

测试4:testParseKVP

void parseKVP(Request req) throws ServiceException {

  preParseKVP(req);

  parseKVP(req, req.getKvp());

测试5:testParseXML

Object parseRequestXML(Object requestBean, BufferedReader input, Request request)
throws Exception {
// check for an empty input stream
if (!input.ready()) {
return null;
}

String namespace = request.getNamespace();
// resolve reader based on root XML element name, may differ from request name (e.g.
// request=GetMap, root element=StyledLayerDescriptor)
String element = request.getPostRequestElementName();
String version = request.getVersion();
String service = request.getService();

XmlRequestReader xmlReader = findXmlReader(namespace, element, service, version);
if (xmlReader == null) {
// no xml reader, just return object passed in
return requestBean;
}

return xmlReader.read(requestBean, input, request.getKvp());
}

findXmlReader方法:在给定请求详细信息的情况下,查找能够读取请求的注册{@link XmlRequestReader}bean

参数:

* @param namespace The XML namespace of the request body
* @param element The OWS request, e.g. "GetMap"
* @param serviceId The OWS service, e.g. "WMS"
* @param ver The OWS service version, e.g "1.1.1"
* @return An {@link XmlRequestReader} capable of reading the request body

测试6:testHelloOperationGet

handleRequestInternal:http://www.cocoachina.com/articles/42993

protected ModelAndView handleRequestInternal(
            HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws Exception {
        preprocessRequest(httpRequest);

        // create a new request instance创建一个request实例
        Request request = new Request();

        // set request / response 设置request/response
        request.setHttpRequest(httpRequest);
        request.setHttpResponse(httpResponse);

        Service service = null;

        try {
            // initialize the request and allow callbacks to override it 初始化request并且允许回调覆写它
            request = init(request);

            // store it in the thread local 将它保存到线程中
            REQUEST.set(request);

            // find the service 寻找服务
            try {
                service = service(request);
            } catch (Throwable t) {
                exception(t, null, request);

                return null;
            }

            // throw any outstanding errors
            if (request.getError() != null) {
                throw request.getError();
            }

            // dispatch the operation 分发该操作
            Operation operation = dispatch(request, service);
            request.setOperation(operation);

            if (request.isSOAP()) {
                // let the request object know that this is a SOAP request, since it effects
                // often how the request will be encoded
                flagAsSOAP(operation);
            }

            // execute it 执行它
            Object result = execute(request, operation);

            // write the response
            if (result != null) {
                response(result, request, operation);
            }
        } catch (Throwable t) {
            // make Spring security exceptions flow so that exception transformer filter can handle
            // them
            if (isSecurityException(t)) throw (Exception) t;
            exception(t, service, request);
        } finally {
            fireFinishedCallback(request);
            REQUEST.remove();
        }

        return null;
    }

Operation类:执行dispatch返回Operation对象

    Operation dispatch(Request req, Service serviceDescriptor) throws Throwable {
        if (req.getRequest() == null) {
            String msg =
                    "Could not determine geoserver request from http request "
                            + req.getHttpRequest();
            throw new ServiceException(msg, "MissingParameterValue", "request");
        }

        // ensure the requested operation exists
        boolean exists = operationExists(req, serviceDescriptor);
        // did we have a mixed kvp + post request and trusted the body for the request?
        if (!exists && req.getKvp().get("request") != null) {
            req.setRequest(normalize(KvpUtils.getSingleValue(req.getKvp(), "request")));
            exists = operationExists(req, serviceDescriptor);
        }

        // lookup the operation, initial lookup based on (service,request)
        Object serviceBean = serviceDescriptor.getService();
        Method operation = OwsUtils.method(serviceBean.getClass(), req.getRequest());

        if (operation == null || !exists) {
            String msg = "No such operation " + req;
            throw new ServiceException(msg, "OperationNotSupported", req.getRequest());
        }

        // step 4: setup the paramters
        Object[] parameters = new Object[operation.getParameterTypes().length];

        for (int i = 0; i < parameters.length; i++) {
            Class<?> parameterType = operation.getParameterTypes()[i];

            // first check for servlet request and response
            if (parameterType.isAssignableFrom(HttpServletRequest.class)) {
                parameters[i] = req.getHttpRequest();
            } else if (parameterType.isAssignableFrom(HttpServletResponse.class)) {
                parameters[i] = req.getHttpResponse();
            }
            // next check for input and output
            else if (parameterType.isAssignableFrom(InputStream.class)) {
                parameters[i] = req.getHttpRequest().getInputStream();
            } else if (parameterType.isAssignableFrom(OutputStream.class)) {
                parameters[i] = req.getHttpResponse().getOutputStream();
            } else {
                // check for a request object
                Object requestBean = null;

                // track an exception
                Throwable t = null;

                // Boolean used for evaluating if the request bean has been parsed in KVP or in XML
                boolean kvpParsed = false;
                boolean xmlParsed = false;

                if (req.getKvp() != null && req.getKvp().size() > 0) {
                    // use the kvp reader mechanism
                    try {
                        requestBean = parseRequestKVP(parameterType, req);
                        kvpParsed = true;
                    } catch (Exception e) {
                        // dont die now, there might be a body to parse
                        t = e;
                    }
                }
                if (req.getInput() != null) {
                    // use the xml reader mechanism
                    requestBean = parseRequestXML(requestBean, req.getInput(), req);
                    xmlParsed = true;
                }

                // if no reader found for the request, throw exception
                // TODO: we may wish to make this configurable, as perhaps there
                // might be cases when the service prefers that null be passed in?
                if (requestBean == null) {
                    // unable to parse request object, throw exception if we
                    // caught one
                    if (t != null) {
                        throw t;
                    }
                    if (kvpParsed && xmlParsed || (!kvpParsed && !xmlParsed)) {
                        throw new ServiceException(
                                "Could not find request reader (either kvp or xml) for: "
                                        + parameterType.getName()
                                        + ", it might be that some request parameters are missing, "
                                        + "please check the documentation");
                    } else if (kvpParsed) {
                        throw new ServiceException(
                                "Could not parse the KVP for: " + parameterType.getName());
                    } else {
                        throw new ServiceException(
                                "Could not parse the XML for: " + parameterType.getName());
                    }
                }

                // GEOS-934  and GEOS-1288
                Method setBaseUrl =
                        OwsUtils.setter(requestBean.getClass(), "baseUrl", String.class);
                if (setBaseUrl != null) {
                    setBaseUrl.invoke(
                            requestBean,
                            new String[] {ResponseUtils.baseURL(req.getHttpRequest())});
                }

                // another couple of thos of those lovley cite things, version+service has to
                // specified for
                // non capabilities request, so if we dont have either thus far, check the request
                // objects to try and find one
                // TODO: should make this configurable
                if (requestBean != null) {
                    // if we dont have a version thus far, check the request object
                    if (req.getService() == null) {
                        req.setService(lookupRequestBeanProperty(requestBean, "service", false));
                    }

                    if (req.getVersion() == null) {
                        req.setVersion(
                                normalizeVersion(
                                        lookupRequestBeanProperty(requestBean, "version", false)));
                    }

                    if (req.getOutputFormat() == null) {
                        req.setOutputFormat(
                                lookupRequestBeanProperty(requestBean, "outputFormat", true));
                    }

                    parameters[i] = requestBean;
                }
            }
        }

        // if we are in cite compliant mode, do some additional checks to make
        // sure the "mandatory" parameters are specified, even though we
        // succesfully dispatched the request.
        if (citeCompliant) {
            // the version is mandatory for all requests but GetCapabilities
            if (!"GetCapabilities".equalsIgnoreCase(req.getRequest())) {
                if (req.getVersion() == null) {
                    // must be a version on non-capabilities requests
                    throw new ServiceException(
                            "Could not determine version", "MissingParameterValue", "version");
                } else {
                    // version must be valid
                    if (!req.getVersion().matches("[0-99].[0-99].[0-99]")) {
                        throw new ServiceException(
                                "Invalid version: " + req.getVersion(),
                                "InvalidParameterValue",
                                "version");
                    }

                    // make sure the versoin actually exists
                    boolean found = false;
                    Version version = new Version(req.getVersion());

                    for (Service service : loadServices()) {
                        if (version.equals(service.getVersion())) {
                            found = true;

                            break;
                        }
                    }

                    if (!found) {
                        throw new ServiceException(
                                "Invalid version: " + req.getVersion(),
                                "InvalidParameterValue",
                                "version");
                    }
                }
            }

            // the service is mandatory for all requests instead
            if (req.getService() == null) {
                // give up
                throw new ServiceException(
                        "Could not determine service", "MissingParameterValue", "service");
            }
        }

        Operation op = new Operation(req.getRequest(), serviceDescriptor, operation, parameters);
        return fireOperationDispatchedCallback(req, op);
    }

service()方法:根据req查找服务

Service service(Request req) throws Exception {
        // check kvp
        if (req.getKvp() != null) {

            req.setService(normalize(KvpUtils.getSingleValue(req.getKvp(), "service")));
            req.setVersion(
                    normalizeVersion(normalize(KvpUtils.getSingleValue(req.getKvp(), "version"))));
            req.setRequest(normalize(KvpUtils.getSingleValue(req.getKvp(), "request")));
            req.setOutputFormat(normalize(KvpUtils.getSingleValue(req.getKvp(), "outputFormat")));
        }
        // check the body
        if (req.getInput() != null && "POST".equalsIgnoreCase(req.getHttpRequest().getMethod())) {
            req = readOpPost(req);
        }

        // try to infer from context
        // JD: for cite compliance, a service *must* be specified explicitley by
        // either a kvp, or an xml attribute, however in reality the context
        // is often a good way to infer the service or request
        String service = req.getService();

        if ((service == null) || (req.getRequest() == null)) {
            Map map = readOpContext(req);

            if (service == null) {
                service = normalize((String) map.get("service"));

                if ((service != null) && !citeCompliant) {
                    req.setService(service);
                }
            }

            if (req.getRequest() == null) {
                req.setRequest(normalize((String) map.get("request")));
            }
        }

        if (service == null) {
            // give up
            throw new ServiceException(
                    "Could not determine service", "MissingParameterValue", "service");
        }

        // load from teh context
        Service serviceDescriptor = findService(service, req.getVersion(), req.getNamespace());
        if (serviceDescriptor == null) {
            // hack for backwards compatability, try finding the service with the context instead
            // of the service
            if (req.getContext() != null) {
                serviceDescriptor =
                        findService(req.getContext(), req.getVersion(), req.getNamespace());
                if (serviceDescriptor != null) {
                    // found, assume that the client is using /
                    if (req.getRequest() == null) {
                        req.setRequest(req.getService());
                    }
                    req.setService(req.getContext());
                    req.setContext(null);
                }
            }
            if (serviceDescriptor == null) {
                String msg = "No service: ( " + service + " )";
                throw new ServiceException(msg, "InvalidParameterValue", "service");
            }
        }
        req.setServiceDescriptor(serviceDescriptor);
        return fireServiceDispatchedCallback(req, serviceDescriptor);
    }

init(req)方法:

    Request init(Request request) throws ServiceException, IOException {
        HttpServletRequest httpRequest = request.getHttpRequest();

        String reqContentType = httpRequest.getContentType();
        // figure out method
        request.setGet("GET".equalsIgnoreCase(httpRequest.getMethod()) || isForm(reqContentType));

        // create the kvp map
        parseKVP(request);

        if (!request.isGet()) { // && httpRequest.getInputStream().available() > 0) {
            // check for a SOAP request, if so we need to unwrap the SOAP stuff
            if (httpRequest.getContentType() != null
                    && httpRequest.getContentType().startsWith(SOAP_MIME)) {
                request.setSOAP(true);
                request.setInput(soapReader(httpRequest, request));
            } else if (reqContentType != null
                    && ServletFileUpload.isMultipartContent(httpRequest)) {
                // multipart form upload
                ServletFileUpload up = new ServletFileUpload();
                up.setFileItemFactory(new DiskFileItemFactory());

                // treat regular form fields as additional kvp parameters
                Map kvpFileItems =
                        new CaseInsensitiveMap<>(new LinkedHashMap<>());
                try {
                    for (FileItem item : up.parseRequest(httpRequest)) {
                        if (item.isFormField()) {
                            kvpFileItems.put(item.getFieldName(), item);
                        } else {
                            request.setInput(fileItemReader(item));
                        }
                    }
                } catch (Exception e) {
                    throw new ServiceException("Error handling multipart/form-data content", e);
                }

                // if no file fields were found, look for one named "body"
                if (request.getInput() == null) {
                    FileItem body = kvpFileItems.get("body");
                    if (body != null) {
                        request.setInput(fileItemReader(body));
                        kvpFileItems.remove("body");
                    }
                }

                Map kvpItems = new LinkedHashMap<>();
                for (Map.Entry e : kvpFileItems.entrySet()) {
                    kvpItems.put(e.getKey(), e.getValue().toString());
                }

                request.setOrAppendKvp(parseKVP(request, kvpItems));
            } else {
                // regular XML POST
                // wrap the input stream in a buffered input stream
                request.setInput(reader(httpRequest));
            }

            if (-1 == request.getInput().read()) {
                request.setInput(null);
            } else {
                request.getInput().reset();
            }
            if (request.getInput() != null && logger.isLoggable(Level.FINE)) {
                char[] req = new char[xmlPostRequestLogBufferSize];
                final int read = request.getInput().read(req, 0, xmlPostRequestLogBufferSize);
                request.getInput().reset();

                if (read < xmlPostRequestLogBufferSize) {
                    logger.fine("Raw XML request: " + new String(req));
                } else {
                    logger.fine("Raw XML request starts with: " + new String(req) + "...");
                }
            }
        }
        // parse the request path into two components. (1) the 'path' which
        // is the string after the last '/', and the 'context' which is the
        // string before the last '/' 解析请求地址,分为两部分,(1)'path'是最后一个'/'之后的字符串,而'context'是最后一个'/'之前的字符串
        String ctxPath = request.httpRequest.getContextPath();
        String reqPath = request.httpRequest.getRequestURI();
        reqPath = reqPath.substring(ctxPath.length());

        // strip off leading and trailing slashes
        if (reqPath.startsWith("/")) {
            reqPath = reqPath.substring(1, reqPath.length());
        }

        if (reqPath.endsWith("/")) {
            reqPath = reqPath.substring(0, reqPath.length() - 1);
        }

        String context = reqPath;
        String path = null;
        int index = context.lastIndexOf('/');
        if (index != -1) {
            path = context.substring(index + 1);
            context = context.substring(0, index);
        } else {
            path = reqPath;
            context = null;
        }

        request.setContext(context);
        request.setPath(path);

        return fireInitCallback(request);
    }

>>参考:https://blog.csdn.net/suen/article/details/4799572

继承AbstractController并重写了之后需要在.xml中配置,配置方法如下:

无论是MVC中的哪一个部分,urlmapping映射器,adapter适配器,controller,viewresolver。。都是bean,都需要配置交由spring来管理,初始化到销毁。

dispatch系统是在1.6之后引入进来的。。

在1.6版之后,GeoServer使用了OWS Dispatching System。它基于Spring Web MVC框架,先把HTTP请求都可以发送到核心控制类org.springframework.web.servlet.DispatcherServlet(前置控制器(Pre Control)),做一些HTTP请求的通用处理(比如文件上载)后,通过合适的派发机制转发给不同的Spring容器中的Bean,也就是不同业务逻辑、服务处理的控制器 (Logic Control),由这些控制器结合不同模型(Model),如空间数据模型、属性数据模型、地图样式模型等,进行处理,形成结果模型,并经 DispatcherServlet返回客户端。从而在Spring的Web MVC的基础上形成GeoServer的MVC架构实作。

不同GeoServer的工程Spring配置文件applicationContext.xml中通过 org.springframework.web.servlet.handler.SimpleUrlHandlerMapping说明不同的URL路径需要不同的业务逻辑控制器做进一步处理。

工程wms、wfs、web、wcs、wcs1_1、rest、gwc等,都有类似的说明,并说明了GeoServer的控制器dispatcher、 putStylesWrapper、filePublisher、restWrapper、gwcRestDispatcher、 geowebcacheDispatcher、geowebcacheDispatcher等做进一步处理。

具体操作指南:

GeoServer的Web工程的配置文件web.xml中说明了那些URL要传递到DispatcherServlet:

1、Servlet声明

 dispatcher

org.springframework.web.servlet.DispatcherServlet

2、URL Mapping

 dispatcher

/wms/*

dispatcher

/wcs/*

 dispatcher

/wfs/*

dispatcher

/ows/*

 类似的还有一些,请参阅web.xml。

最新版的geoserver,所有的url都交由dispatcher类处理。