WCF & Web Proxy
2020-11-16
0. 前言
好消息!昨天求助的那个问题解决了,趁记忆还清晰,这篇文章就来复盘一下走过的弯路以及学到的一些知识。
1. 背景概述
项目架构(修改前)
在整个项目里面,我们有两个单独的 project:
-
一个是 Console App,不需要改动;
-
另一个(红色)是我们用来和 Client Web Service 进行数据交换的 Web Proxy(代理服务器);
因为 Client Service 是用 SOAP API,我们的代理服务器就用了 WCF 这个框架。
项目架构(修改后)
因为多了一层 OAuth 认证,我们需要先拿到一个 token,然后在 request 的时候把这个 token 加在 header 里面。
这个任务的核心在于,在 WCF proxy 执行 SOAP request 之前要把 token 加到 header 里面。
2. 为什么卡住
如果对上面的术语不了解的话,建议先看完后面的部分,最后再回来看这部分。
昨天的情况是,我查到了一种在 http request 里面添加 header,把 token 放进去的方法,就是下面这样,在 GetData 之前,先在 scope 里面把 token 加进去。
// ...........
var client = new DemoWebService.WebServiceSoapClient();
using (var scope = new OperationContextScope(client.InnerChannel))
{
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = $"Bearer {token}";
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
var data = client.GetData();
}
// ....................
但是因为对整个项目、WCF、SOAP 等不了解……我在测试的时候发现有这样一个 error。
这个 exception 正好是在 SOAP request 之前出现的,所以我的第一反应是:proxy 设置的不对。
然后我的研究方向就从【给单个 method 加 token】变成了【如何给整个 service reference 加 token】。
因为添加 service 的时候,是直接在 UI 里面点的,没有看到任何可以加 token 的地方。
于是我的研究方向又变成了【如何通过代码,而不是 UI,来添加 service reference】。
当然也有人会说,为什么非要添加这个 reference 呢?
这里就涉及到了 WSDL 的概念,这个文件是用来定义 SOAP API 的接口的,通过 WCF 添加 WSDL 文件,就不需要自己单独写代码来解析 SOAP API 传回来的数据,省掉很多不必要的麻烦。
这个项目只给了 3 天时间就要完成,所以放弃已有的 proxy service 做法,自己写接口也不太现实。
如何用代码添加 reference
这个做法是存在的,我们之前也有项目用过类似的方法,大概就是把逻辑写在 SSIS 的 dtsx 文件里面,创建 ChannelFactory 和 IMessagingInterface,override PreExecute() 的时候把 token 加进去。
听起来很简单,但是在一知半解的情况下真的有点懵。
没想到……
正当我准备撸起袖子研究 ChannelFactory 的时候,一个同事跟我说,你这个 error message 看起来没什么大碍,可以试着按一下 “Continue”,看看之后会发生什么。
What!!!!?
这个帖子 里面有人遇到了同样的问题,回复是【别管它,继续执行就行了】。
“Believe it or not, this is normal behaviour. An exception is thrown but handled by the XmlSerializer, so if you just ignore it everything should continue on fine.”
于是我半信半疑地按了【continue】,然后……奇迹发生了,我的代码运行一切顺利,任务完成!!
为什么不需要在 service reference 那边加 token?
复盘之后,我发现自己对于 reference 的理解有偏差,我以为一定要先看到对方 server 的 API index 页面,才能发送请求,要看到这个页面,就必须有 token 才行。
但是,我们的代理服务器跟这个 index 页面根本没关系,我们只是需要对方的 WSDL 文件来对收到的数据进行解析。
所以在单个 request 里面添加 header 就可以了(又回到了一开始的解决方案)。
卡住的原因
自身的能力有限
- 对这个项目用到的一些技术、术语都不太了解,所以对一开始的解决方案(request 里面加 header)没有足够的信心;
- 这个项目一开始用的是 .NET Framework 4.0,我升级到了 4.7.2,无法确定这个升级是否影响了项目运行;
- 没有见过这个项目在生产环境是如何运行的,也是第一次接手这个项目,所以不知道什么样的情况是对的(proxy server 运行时到底能不能看到 API index),什么样的情况是错的……
因为这些不确定的因素,我就只能默认一切都可能出问题。
没有认真查 error message
基于以上三点,我在看到了 xml error 的时候,马上推翻了自己的解决方案,而没有去查这个 error 的出现原因是什么,想当然地以为是 service reference 出了问题;
开始走歪
由于第一个方案被我自己推翻了,我有了新的思路(其实是想多了)。
- 研究第二个方案的时候,发现 service reference 的 UI 没法直接添加 token,于是又陷入了无奈……脑子转过弯来才想起来,可以用代码创建 reference;
- 用代码创建 reference 的做法对我来说非常陌生,又要研究一大堆东西(┭┮﹏┭┮);
综上所述,假如能认真查一下 error message 就好了…… 大家一定要引以为戒啊!!
虽然走了点弯路,但是也顺便了解了第二个方案涉及到的知识,没准以后需要用到(毕竟我们另外一个项目已经用了类似的做法),所以对自己也是有好处的。
下面总结一下我这几天学到的知识点。
3. 术语简介
SOAP API
SOAP is an XML-based protocol that lets you exchange info over a particular protocol (can be HTTP or SMTP, for example) between applications. It stands for Simple Object Access Protocol and uses XML for its messaging format to relay the information.
SOAP API 是基于 XML 的协议,可以通过 HTTP 或者 SMTP 来传输。
我们的项目用的就是 HTTP,所以在 request header 里面加 token 就可以。
然而我当时以为要给整个 service reference request 加 token。
SOAP API 年代比较久远,现在基本上已经被 RESTful API 取代了(但老项目还是有用 SOAP 的= =)。
WCF (Windows Communication Foundation)
Windows Communication Foundation (WCF) is a framework for building service-oriented applications. Using WCF, you can send data as asynchronous messages from one service endpoint to another. A service endpoint can be part of a continuously available service hosted by IIS, or it can be a service hosted in an application.
Normally, a WCF service will use SOAP, but if you build a REST service, clients will be accessing your service with a different architectural style (calls, serialization like JSON, etc.). Exposing a WCF service with both SOAP and REST endpoints, requires just a few updates to the codebase and configuration.
WCF 是一个用来创建服务型应用的框架,WCF 可以把一个 endpoint 的数据传给另一个 endpoint。
WCF 一般来说都是跟 SOAP 结合使用的。
WSDL (Web Services Description Language)
WSDL is an XML format for describing network services as a set of endpoints operating on messages containing either document-oriented or procedure-oriented information.
WSDL files are central to testing SOAP-based services.
WSDL 是用来描述 SOAP web service 的接口的文件,对于测试 SOAP service 非常重要。
我一开始以为,这个 WSDL 文件也被 token 保护起来了,后来才发现客户可以直接给我们这个 WSDL 的 endpoint……
SSIS (SQL Server Integration Services)
SSIS is a platform for data integration and workflow applications. It features a data warehousing tool used for data extraction, transformation, and loading (ETL). The tool may also be used to automate maintenance of SQL Server databases and updates to multidimensional cube data.
SSIS 是 .NET 跟数据库交互的方式。
DTS (Data Transformation Services)
Data Transformation Services (DTS) file used by SQL Server, an RDBMS (relational database management system) application; specifies settings for migrating data between databases; allows the integration of external data sources. DTSX files are used by SSIS (SQL Server Integration Services), a component of SQL Server.
SSIS 使用 DTSX 文件来和 SQL Server 进行交互。
dtsx 文件包括 xml 和 script 两部分。
为什么需要 SSIS ?
SSIS 的方案是在我认为单独 request 加 header 无效之后被提出来的。
dtsx 的 script 部分写的是 ChannelFactory 和 PreExecute() 相关的代码,
这个方案可以直接从 client endpoint 那边拿数据,写入数据库,可以在不依赖 WCF Proxy 自动生成 config 的情况下调用客户的 WCF service。
PS: 如果真的要采用这个方案,我还是需要花蛮多时间去理解整个逻辑和具体的实现方法的。
4. Demo
下面我们来尝试复原一下项目架构,创建一个 WCF Proxy 项目和一个 Console App,把这两个项目连起来。
因为 WCF 这个框架很老了,所以 Visual Studio 2019 版本需要单独安装一个包才可以创建 WCF project。
1. 创建项目 - WCF Service Application
Console App 需要用到我们的代理服务器,代理服务器会部署在 IIS。
测试
在什么都不改的情况下,运行 Project。
然后会跳出一个提示,让你添加 Service(忘记截图了,点确定就可以)。
接着再点运行,就可以看到这个页面。
这个Service1.svc
就是我们的 Server 页面。
2. 创建一个 Console App
给这个 Console App 添加 Service Reference (我们的 WCF proxy service)
PS: 刚才的 WCF Server 保持运行哦~
Data Type 和 Client Option 保持默认配置。
Visual Studio 自动生成代码。
Note
在 Console App 里面添加 Service 和在 WCF 代理服务器里面添加 Service 的步骤是一样的。
正如我之前提到的,通过 UI 添加 Service 的时候是没法加 token 的,所以我昨天就在这里卡了很久。
5. 结语
这个任务涉及到的技术都是我第一次接触的,所以一开始理解起来有点困难,周末也花了很长时间做 research。
这篇文章总结了一下我目前学到的知识(和踩到的坑),SSIS 的部分没有仔细研究,以后如果遇到类似的任务再来写后续。
最后,谢谢昨天帮忙分析问题的小伙伴们,你们说的没错,request 里面加 header 就好了,做完之后回顾整个过程,发现还是挺简单的。