当前位置:   article > 正文

GeoServer源码解读 - 入参处理_geoserver源码分析

geoserver源码分析

GeoServer源码解读 - 解析kvp和xml格式参数。(GeoServer版本 2.21)

1. 前言

最近遇到一些需求,所以读了下GeoServer源码,因为源码相关体系比较庞大,所以想着就一些细节进行归纳,整理下体系脉络,以备不时之需。

不出意外的话,这将会一个系列文章,主题是围绕GeoServer后端服务的主体处理逻辑进行源码研读。

正如标题所言,本文将主要关注GeoServer在主体处理流程中,对于接收参数的处理。(至于整体处理逻辑快速浏览,推荐看一下这篇2011年的博客GeoServer源码解析和扩展 (一)基础篇

2. 概述

作为GeoServer主体逻辑流程核心类的Dispatcher,其中与parse相关的方法如下:
在这里插入图片描述

再结合GeoServer对于参数的接收是基于HTTP协议这一条脉络,因此其参数接收分为如下两类:

HTTP-METHODHTTP-CONTENT TYPE入参数据格式关键处理类隶属
GETx-www-form-urlencodedkvpKvpParser / KvpRequestReaderGeoServer自建
POSTXML request bodyxmlXmlRequestReader基于GeoTools

流程图版本参见: GeoServer2.21 - 入参处理

3. kvp格式的处理

在GeoServer业务逻辑处理的主体链条Dispatcher.handleRequest(request, response)中,处理kvp格式数据的关键方法大约有如下几个:

  1. KvpUtils.parse(Map<String, Object> kvp) 。涉及关键接口KvpParser
  2. Dispatcher.parseRequestKVP(Class<?> type, Request request)。涉及关键接口KvpRequestReader
3.1 接口KvpParser

KvpParser实际表现是一个抽象类,用于封装一些公用逻辑。按照官方解释,该接口主要负责将 key-value pair 转换为 key-object pair 。

  1. 其核心方法为Object parse(String value)。子类需要实现各自对应的自定义解析逻辑。
  2. 子类构造函数中需要提供两个参数:第一个参数指示"key-value pair"中的key名称,例如FilterKvpParser所声明的"filter",第二个参数指示的则是调用核心方法为parse(String value)之后的返回值类型。关于这一点典型例子可以参见FilterKvpParser,其所对应的入参为FILTER=(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="states.3"/></Filter>)
  3. 关于GeoServer从注册到Spring容器中的诸多KvpParser实例中挑出与之最为匹配的,决定条件之一是前端传递来的key-value pair中key的值。这一点和下面马上要讲到的KvpRequestReader是完全不同的。

该接口拥有着数量众多的子类:
在这里插入图片描述
以上截图所展示的只是KvpParser 子类中的一部分。GeoServer在接收到前端传递来的kvp格式参数后,将从注册到Spring容器中的诸多KvpParser实例中挑出与之最为匹配的,相关挑选规则在KvpUtils.findParser(final String key, final String service, final String request, final String version, Collection<KvpParser> parsers)中可以找到:

public static KvpParser findParser( final String key, final String service, final String request, final String version, Collection<KvpParser> parsers) {
	// find the parser for this key value pair
	KvpParser parser = null;
	final Iterator<KvpParser> pitr = parsers.iterator();
	// 本次循环中没有break关键字,所以肯定是整体迭代一次, 以期找出最合适的Parser
	while (pitr.hasNext()) {
		KvpParser candidate = pitr.next();
		// 1. 比较key
		// key: 前端传参时的key-value pair中的key
		// candidate.getKey() : 构建KvpParser实例的构造函数参数key
		// 只有以上两者向匹配, 即key相同, 才会进入备选列表
		if (key.equalsIgnoreCase(candidate.getKey())) {
			if (parser == null) {
				parser = candidate;
			} else {
			    // 2. 比较service
				// if target service matches, it is a closer match
				String trgService = candidate.getService();
				if (trgService != null && trgService.equalsIgnoreCase(service)) {
					// determine if this parser more closely matches the request
					String curService = parser.getService();
					if (curService == null) {
						parser = candidate;
					} else {
					    // 3. 比较version
						// both match, filter by version
						Version curVersion = parser.getVersion();
						Version trgVersion = candidate.getVersion();
						if (trgVersion != null) {
							if (curVersion == null && trgVersion.toString().equals(version)) {
								parser = candidate;
							}
						} else {
							if (curVersion == null) {
								// ambiguous, unable to match
								throw new IllegalStateException(
										"Multiple kvp parsers: " + parser + "," + candidate);
							}
						}
					}
				}
			}
		}
	}
	return parser;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

所以只有 key, service, version全部匹配,才是真正处理当前key-value Item的。

入参在经过一组对应的KvpParser处理之后,将成为相应的Map<String, Object>实例,传递给之后的逻辑。所以KvpParser只是参数处理的第一阶段。

3.2 接口KvpRequestReader

KvpRequestReader实际表现是一个抽象类,封装一些公用逻辑。按照官方解释,该接口主要负责将 a kvp set 转换为 a request bean。

  1. 很明显,该接口的执行是在前面的KvpParser生效之后。

  2. 核心方法read(Object request, Map<String, Object> kvp, Map<String, Object> rawKvp) ,其中第二个方法参数Map<String, Object> kvp正是KvpParser处理后的结果。

  3. 关于GeoServer从注册到Spring容器中的诸多KvpRequestReader实例中挑出与之最为匹配的,完全取决于后端方法定义中的方法参数声明,典型例子是DefaultWebFeatureService20.getCapabilities(GetCapabilitiesType request)。(注意这一点是和上面的KvpParser挑选逻辑是不一样的)。

    // Dispatcher.java
    public static KvpRequestReader findKvpRequestReader(Class<?> type) {
        // 从Spring容器中抓取注册过的KvpRequestReader实现类
    	Collection<KvpRequestReader> kvpReaders = loadKvpRequestReaders();
    
    	List<KvpRequestReader> matches = new ArrayList<>();
    
    	for (KvpRequestReader kvpReader : kvpReaders) {
    		// 这个 kvpReader.getRequestBean() 返回的值, 是由构造函数参数参入的, 这样保证了约束性, 范例参见 org.geoserver.wfs.kvp.GetCapabilitiesKvpRequestReader
    		if (kvpReader.getRequestBean().isAssignableFrom(type)) {
    			matches.add(kvpReader);
    		}
    	}
    
    	if (matches.isEmpty()) {
    		return null;
    	}
    
    	if (matches.size() > 1) {
    		// sort by class hierarchy
    		Comparator<KvpRequestReader> comparator =
    				(kvp1, kvp2) -> {
    					if (kvp2.getRequestBean().isAssignableFrom(kvp1.getRequestBean())) {
    						return -1;
    					}
    
    					return 1;
    				};
    
    		Collections.sort(matches, comparator);
    	}
    	
    	// 择优录取
    	return matches.get(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
3.3 kvp解析范例

以下范例参数来自GeoServer官方Demo。

// ============ 入参
{GeoServer-RootUrl}?request=GetFeature&version=1.1.0&typeName=topp:states,tiger:tiger_roads&outputFormat=GML2&FILTER=(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="states.3"/></Filter>)(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="tiger_roads.3"/></Filter>)'

// ============ typeName=topp:states,tiger:tiger_roads
1. 负责处理这段的是 TypeNameKvpParser 类。
2. 转换出来的对象类型: QName// ============ FILTER=(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="states.3"/></Filter>)(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="tiger_roads.3"/></Filter>)
1. 负责处理这段的是 Filter_1_1_0_KvpParser 类。(其中对于parse方法的实现借用了下面的xml解析)
2. 转换出来的对象类型为: FeatureIdImpl + FidFilterImpl
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4. xml格式的处理

处理xml格式数据的关键方法大约有如下几个:

  1. Dispatcher.readOpPost(Request req) 。解析必要的基础参数,例如xml相关的namespace,通用参数service,version,outputFormat等等。
  2. Dispatcher.parseRequestXML(Object requestBean, BufferedReader input, Request request) 。进行实际的 xml 到 request bean的转换。
4.1 接口XmlRequestReader

XmlRequestReader实际表现是一个抽象类,用于封装一些公用逻辑。按照官方解释,该接口主要负责将 xml 转换为a request bean 。

  1. 其核心方法为read(Object request, Reader reader, Map kvp)。子类需要实现各自对应的自定义解析逻辑。
  2. 关于GeoServer从注册到Spring容器中的诸多XmlRequestReader实例中挑出与之最为匹配的,只有 namespace(例如"http://www.opengis.net/wfs"), element(例如"GetFeature"), serviceId(例如"WFS"),version(例如"1.1.0")全部匹配,然后在此基础上进行择优录取。相关实现源码参见:Dispatcher.findXmlReader(String namespace, String element, String serviceId, String ver)
4.2 xml解析范例

以下范例参数来自GeoServer官方Demo。

<!--对应:net.opengis.wfs.impl.GetFeatureTypeImpl 类,, org.geoserver.wfs.xml.v2_0.GetFeatureTypeBinding 类来处理 ...-->
<wfs:GetFeature service="WFS" version="1.1.0" >
	<!--对应:net.opengis.wfs.impl.QueryTypeImpl类,, 使用 QueryTypeBinding.parse(ElementInstance instance, Node node, Object value) 进行解析的...-->
	<wfs:Query typeName="topp:states">
		<wfs:PropertyName>topp:STATE_NAME</wfs:PropertyName> 
        <!-- org.geotools.filter.AttributeExpressionImpl , 使用 org.geoserver.wfs.xml.filter.v1_1.PropertyNameTypeBinding 进行解析 -->
		<wfs:PropertyName>topp:LAND_KM</wfs:PropertyName> 
		<wfs:PropertyName>topp:the_geom</wfs:PropertyName> 
        <!-- 处理堆栈图见下方 ; -对应:org.geotools.filter.IsBetweenImpl本身就是继承自Filter接口 ,, 使用 org.geoserver.wfs.xml.filter.v1_1.FilterTypeBinding.parse(ElementInstance instance, Node node, Object value) 进行解析的 -->
		<ogc:Filter>
        	<!-- org.geotools.filter.IsBetweenImpl , 使用 OGCPropertyIsBetweenTypeBinding.parse(ElementInstance instance, Node node, Object value) 进行解析 -->
			<ogc:PropertyIsBetween>
				<ogc:PropertyName>topp:LAND_KM</ogc:PropertyName>
                <!--  对应的binding为: OGCLowerBoundaryTypeBinding, 解析出来的其实还是里面的 org.geotools.filter.LiteralExpressionImpl  -->
				<ogc:LowerBoundary>
                	<!-- 匹配: org.geotools.filter.LiteralExpressionImpl -->
					<ogc:Literal>100000</ogc:Literal>
				</ogc:LowerBoundary>
                <!-- 对应的binding为: OGCUpperBoundaryTypeBinding -->
				<ogc:UpperBoundary>
					<ogc:Literal>150000</ogc:Literal>
				</ogc:UpperBoundary>
			</ogc:PropertyIsBetween>
		</ogc:Filter>
	</wfs:Query>
</wfs:GetFeature>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在这里插入图片描述

相较于kvp,GeoServer中针对xml处理其实是在GeoTools基础上进行的些许扩展,主体逻辑和思路其实是在GeoTools中完成的,限于篇幅以及本文中心,这里就不再累述,感兴趣的读者可以通过本地底部链接查看下GeoTools对XML处理的相关考量和相应解决方案。笔者之后也会整理一份相关的博客。

5. 关于DispatcherCallback接口

这个接口并不完全属于本小节内容,但因为我们可以借助它实现某些参数解析方面的扩展,因此这里作一个关联性介绍。

GeoServer在启动时会加载注册到Spring容器中的DispatcherCallback接口实现类,然后在实际进行请求处理时候,在不同的阶段回调不同的DispatcherCallback接口方法。详细的对应关系参见: GeoServer2.21 - DispatcherCallback接口回调契机

补充:

  1. 实际扩展时可以借助AbstractDispatcherCallback抽象类。
    在这里插入图片描述

6. 总结

  1. GeoServer中针对参数处理分为kvp和xml两种。其中kvp格式要求以HTTP GET + 表单方式发起调用,xml则是HTTP POST + xml body方式发起。
  2. 针对kvp参数, GeoServer抽象出了KvpParserKvpRequestReader进行读取转换。
  3. 针对xml参数, GeoServer则是抽象出了XmlRequestReader进行读取转换。这里需要注意的是可以看出GeoServer在命名上进行了非常刻意地设计,例如KvpRequestReaderXmlRequestReader接口命名,以及Dispatcher类中的parseRequestKVPparseRequestXML方法命名,都属于一眼就能看出其作用,并且能够猜出对应另外一个方法名。

7. 参考

  1. OGC® Web Feature Service 2.0 Interface Standard
  2. GeoTools官网 - XML
  3. GeoServer源码解析和扩展 (二)注册服务 - KvpReader
  4. GeoServer源码解析和扩展 (三)结构篇 - KvpParser
  5. 开放GIS标准OGC之路(3)之 WFS初探 – 这篇 09年的文章,现在看来,一点都没过时…
  6. 开放GIS标准OGC之路(4)之 解密Filter
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号