GeoServer之Dispatcher类
Dispatches an http request to an open web service (OWS). 向OWS开放网络服务转发(派发)http网络请求
An OWS request contains three bits of information: 一个OWS请求包含三点信息:
- The service being called被叫的服务
- The operation of the service to execute执行的服务的操作
- 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 MapkvpFileItems = 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类处理。