SAP OData服务调用之C#篇
版本:V1.0
编写人:杨大山
日期:2018-01-11
目 录
一、 认识ODATA 3
二、 常见异常 3
1、 远程服务器返回错误: (401) 未经授权; 3
2、 远程服务器返回错误: (404) NOT FOUND; 3
3、 HTTP/1.1 405 METHOD NOT ALLOWED; 3
4、 远程服务器返回错误: (400) BAD REQUEST; 3
5、 远程服务器返回错误: (403) 已禁止; 3
三、 分析思路 3
1、 背景说明 3
2、 思路重现 4
四、 解决方案 6
1、 必须知道 6
2、 核心代码之RESTSHARP 7
3、 核心代码之HTTPWEBREQUEST 8
五、 温馨提示 9
一、 认识OData
定义:Open Data Protocol,开发数据协议,谓之OData,它是用来查询和更新数据的一种Web协议;
理解:OData is an Online Database Connectivity(ODBC) for the Web;
须知:OData 是实现Restful Web接口风格的最佳方式。
二、 常见异常
1、 远程服务器返回错误: (401) 未经授权;
2、 远程服务器返回错误: (404) Not Found;
3、 HTTP/1.1 405 Method Not Allowed;
4、 远程服务器返回错误: (400) Bad Request;
5、 远程服务器返回错误: (403) 已禁止;
Validierung des CSRF-Tokens fehlgeschlagen(德语:CSRF 令牌验证失败);
以上为C#后台调用SAP OData最为常见的几种异常,尤以第3项最为常见与复杂。
三、 分析思路
1、 背景说明
笔者熟悉C#、ABAP、JAVA等语言,具有JAVA、AJAX与SAP OData成功对接经验;
相关OData已在SAP环境测试通过,服务正常;
相关参数为SAP方提供,且参数格式与内容经SAP GUI内置 SAP Gateway客户端测试正常,该步骤极为必要,若没有进行,须烦请ABAP人员协助验证之(理论上ABAP开发人员再发布接口给调用方时,已完成相关验证),如图示一:
图示一
已知在对URI资源的增(PUT或POST)、删(DELETE)、改(PUT或POST)时,需要携带已授权且未失效的Token信息,而对资源的查询(GET)则不需要,是故当HTTP请求方式为PUT、POST、DELETE时,须先对URI资源GET请求一次(握手),握手成功后,SAP会返回Token信息,再次请求即可携带之;
Token的名称为:X-CSRF-Token;
2、 思路重现
任何异常,除明显能锁定原因外,均请在处理之前优先咨询接口提供方(SAP端)是否有捕获到异常(通过事务代码/N/IWFND/ERROR_LOG即可查看),SAP端若捕获到异常,则ABAP人员可先结合SAP端的异常堆栈信息解决或给出方向性建议(因为调用方多半没有SAP开发经验,异常无从查起)。
401 Unauthorized(未经授权)
请求未携带身份验证信息或身份验证信息错误。
404 Not Found(资源未找到)
请检查urI路径的合法性、准确性,当你从代码层面看不出url地址有任何问题时,依然报404的话,请在调试模式下(运行时)查看HTTPRequest中的请求地址信息,例如以下代码重现(图示二):
图示二
调试模式下抓取Response信息,不难发现,urI有异常,如图示三:
图示三
利用抓包工具Fiddler抓取的信息,url路径异常,一目了然,如图示四:
图示四
正确代码(图示五):
图示五
405 Method Not Allowed
一般为URI路径错误,在增、删、改接口的URI路径中包含了多余字符,非GET请求,URI路径末尾不能添加任何参数及字符。
400 Bad Request
该错误多半在接口提供方(ABAP)会有异常堆栈信息输出(如何查看,不再赘述),故优先咨询接口提供方具体的异常信息,以便排查,常见为参数格式或内容不合法(SAP端提示为“元数据解析错误”)、日期及时间格式转换错误、接口提供方代码异常等。
403 Forbidden(已禁止)
该异常较为常见,这里重点分析之。
背景描述:笔者的同事尝试了很多HTTP请求工具类,皆无功而返,止步于403异常,吾Review了几遍代码,未见不当之处,于是乎,抛开代码开始整理思路;
思路再现:
首先,查看HTTP Response响应的内容,提示403 Forbidden,令牌(X-CSRF-Token)验证失败,可令牌为前次GET请求获取到的,不可能是非法的令牌;
其次,前面说过,笔者是有SAP Odata服务调用之JAVA端调用成功经验的,于是打开之前的java项目,Review相关内容,核对请求的Header及Body信息,发现与现有C#项目并无差异,因JAVA这边请求Odata的工具类是RestTemplate(第三方工具类),于是决定放弃使用HttpWebRequest、WebClient等请求处理类,改为选取一个专门用于请求REST风格接口的工具类,如:RestSharp、Simple.OData.Client等;
进而,笔者选取RestSharp工具类,通过Nuget命令安装完相关依赖后,编码实现,结果亦然止步于403错误,至此,吾已万念俱灰,独想静静;
再次,片刻思忖后,我决定出大招,既然JAVA这边的调用能够成功,况且使用类似谷歌浏览器REST客户端的工具模拟请求均能成功,那我是否能借助抓包工具将成功与不成功的请求的Header和Body信息抓取下来,一一对比,寻找差异。
最后,果不其然,发现差异,成功情况第二次请求的头信息里含Cookie,而失败情况的二次请求里没有Cookie信息,如下(图示六),故决定往第二次请求的Header里手动添加第一次响应结果返回的Cookie信息,终于出现201,Created,至此,Success!
图示六
四、 解决方案
1、 必须知道
PUT、POST、DELETE请求前,须先发送GET请求,与SAP服务器握手,获取Token(X-CSRF-Token);
二次请求头(Request Header)信息须包含一下内容;
X-Requested-With:XMLHttpRequest;
ContentType:application/json;odata=verbose;charset=utf-8;
DataServiceVersion:2.0
X-CSRF-Token:第一次请求的响应内容里抓取;
Method:PUT or POST or DELETE
Accept: application/json;odata=verbose(如果希望返回xml格式,则为application/atom + xml)
请求参数放入二次请求体(Body)里;
二次请求须携带第一次请求返回的Cookie信息;
2、 核心代码之RestSharp
private void Form1_Load(object sender, EventArgs e)
{
#region RestSharp
var client = new RestClient(“http://yourserver:port/sap/opu/odata/sap/ZODATA_PP_EKP_DZCFN_SRV/TOULIAO_ITEMSet“);
client.Authenticator = new HttpBasicAuthenticator(“306”, “12345678”);
var request = new RestRequest(Method.GET);
request.AddHeader(“X-Requested-With”, “XMLHttpRequest”);
request.AddHeader(“Accept”, “application/json”);
request.AddHeader(“DataServiceVersion”, “2.0”);
request.AddHeader(“X-CSRF-Token”, “Fetch”);
request.AddHeader(“Content-Type”, “application/atom+xml;charset=utf-8”);
RestResponse response = (RestResponse)client.Execute(request);
var request2 = new RestRequest(Method.POST);
request2.AddHeader(“X-Requested-With”, “XMLHttpRequest”);
request2.AddHeader(“DataServiceVersion”, “2.0”);
request2.AddHeader(“Content-Type”, “application/json;odata=verbose”);
request2.AddHeader(“Accept”, “application/json;odata=verbose”);
request2.AddHeader(“X-CSRF-Token”, response.Headers.Where(p => p.Name.ToLower() == “x-csrf-token”).ToList()[0].Value.ToString());
response.Cookies.ToList().ForEach((c) => { request2.AddCookie(c.Name, c.Value); });
var postData = new
{
Docln = “000011”,
Zdate = “20171116”,
Zshij = “174950”,
Zlman = “1414”,
Werks = “6100”,
Aufnr = “80000087”,
MatnrCp = “000000000000010019”,
MaktxCp = “红油笋丝”,
MatnrZj = “000000000000010804”,
MaktxZj = “花椒面”,
MengeBz = “1.000”,
MengeLj = “0.000”,
Meins = “KG”
};
request2.AddJsonBody(postData);
var response2 = client.Execute(request2);
#endregion
}
3、 核心代码之HttpWebRequest
private void Form1_Load(object sender, EventArgs e)
{
System.Net.HttpWebRequest request = System.Net.WebRequest.Create(“http://yourserver:port/sap/opu/odata/sap/ZODATA_PP_EKP_DZCFN_SRV/?$format=json“) as HttpWebRequest;
request.Credentials = new NetworkCredential(“306”, “12345678”);
request.ContentType = “application/json;odata=verbose;charset=utf-8”;
request.Headers.Add(“X-Requested-With”, “XMLHttpRequest”);
request.Headers.Add(“DataServiceVersion”, “2.0”);
request.Headers.Add(“X-CSRF-Token”, “Fetch”);
request.Accept = “application/json;odata=verbose”;
request.Method = “GET”;
//下面这句很重要,否则该request对应的response将无法获取Cookie
request.CookieContainer = new CookieContainer();
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
StreamReader reader = new StreamReader(response.GetResponseStream());
Console.WriteLine(reader.ReadToEnd());
string strParm = “{ \”Docln\” : \”000003\”, \”Zdate\” : \”20171116\”, \”Zshij\” : \”174950\”, \”Zlman\” : \”1414\”, \”Werks\” : \”6100\”, \”Aufnr\” : \”80000087\”, \”MatnrCp\” :\”000000000000010019\”, \”MaktxCp\” : \”红油笋丝\”, \”MatnrZj\” : \”000000000000010804\”, \”MaktxZj\” : \”花椒面\”, \”MengeBz\” : \”1.000\”, \”MengeLj\” : \”0.000\”, \”Meins\” : \”KG\”}”;
byte[] btBodys = Encoding.UTF8.GetBytes(strParm);
HttpWebRequest request2 = WebRequest.Create(“http://yourserver:port/sap/opu/odata/sap/ZODATA_PP_EKP_DZCFN_SRV/TOULIAO_ITEMSet“) as HttpWebRequest;
//下面这一句很重要,携带令牌Token
request2.Headers.Add(“X-CSRF-Token”, response.Headers.Get(“X-CSRF-Token”));
request2.ContentType = “application/json;odata=verbose”;
request2.Headers.Add(“X-Requested-With”, “XMLHttpRequest”);
request2.Headers.Add(“DataServiceVersion”, “2.0”);
//下面这句将决定response响应的内容格式为json
request2.Accept = “application/json;odata=verbose”;
request2.Method = “POST”;
request2.Headers.Add(HttpRequestHeader.Authorization, request.Headers[HttpRequestHeader.Authorization]);
//下面两句很重要,否则会报403
request2.CookieContainer = new CookieContainer();
request2.CookieContainer.Add(response.Cookies);
request2.GetRequestStream().Write(btBodys, 0, btBodys.Length);
HttpWebResponse = (HttpWebResponse)request2.GetResponse();
StreamReader = new StreamReader(httpWebResponse.GetResponseStream());
string responseContent = streamReader.ReadToEnd();
httpWebResponse.Close();
streamReader.Close();
}
}
五、 温馨提示
SAP OData服务调用之JAVA,建议使用工具类RestTemplate;
SAP OData服务调用之OPEN UI5(AJAX),IDE环境若为Eclipse,建议请求地址为:proxy/http/youserver:port/sap/opu/odata/sap/服务名,以服务代理的方式发送请求,否则会报403错误。