WebService调用的开发记事(广州玄武科技)
阅读本文必备知识(Http协议、Postman的使用方法),话说这两项都不懂,你都不好意思说自己是码畜😓(真的不懂Http的,我向你推荐罗剑锋老师的【透视Http协议】,至于postman,懂Http后自然就懂了)
本次接到任务要开发发送短信功能,短信服务商是“广州玄武科技”,业务只要求开发单发短信的功能,所以我在文档中找到了我需要的信息,截图如下
下面是整个处理过程,首先声明,本人在开发前对WebService根本不懂
1、既然不懂,先上网搜一搜吧,大致了解到,WebService也是一种应用层传输协议,基于Http。那好办,发个请求就完事了,但。。。请求的uri,请求头,body怎么写,这些我看完文档是完全不懂的(也许学过WebService的人能懂)
2、既然不能确定如何发请求,就上客户的服务器上,用postman发几个请求试试,首先先发这个我在文档上看到的第一个uri吧【http://IP :PORT/Service/WebService.asmx?wsdl】(Get请求,虽然文档没写Get还是Post,但两种都试一下就知道了,一般不会出现第三种,至少我码了3年代码都没出现过),结果如下,有东西返回,不错。返回值是xml格式,里面有文档提到的那些接口,估计这得是一个接口的说明文档
3、尝试调用其他的接口,先找个“查询”的接口吧(GetAccountInfo),我想不同方法会不会是直接在uri中拼上,类似“http://192.168.5.103:8888/Service/GetAccountInfo”或“http://192.168.5.103:8888/Service/WebService.asmx/GetAccountInfo”,残念ながら、間違ったみたい。两个调用都失败了
但第二种不是404,值得再研究,又观察第二种的返回跟不加GetAccountInfo的是一样的
于是就猜测,这个方法名称可能不是加在url里的,那么还可能是uri传参或者body传参传过去
4、寻找方法名传递的方法,上网搜寻WebService的postman调用,没有发现有人说是uri传参的,那就只剩下body传参,既然是body传参,就要顺便抄抄怎么写,以下是其中一个博文的截图
这才恍然大悟,そうですね、試してみようか,不管对不对,赶紧试一下,请求方法自然是Post,因为Get没有body。注意一点,既然是要在body传xml了,请求头自然要加上(不然就会返回
5、编写body,抄归抄,不能完全抄,更不要复制粘贴,因为有个“0宽空格”,之前就踩过这个坑。我结合获取文档的返回、错误信息的返回,编写了如下请求(当然也不是一次就改成这样,也失败了几次,期间发送过去的请求一般返回xml解析失败的错误,然后看一下自己哪里格式写错了,改改)
终于看到上面的返回值,为找不到分派方法,估计是这个xmlns的锅,猜测是这个xmlns不对,我重新看到【http://IP :PORT/Service/WebService.asmx?wsdl】的返回值,发现在方法名后面有个tns,于是我就想,这个可能是这个方法的一个域,只有在这个域里面才有这个方法
然后,我加了一个xmlns:tns,跟返回值一样的,把方法前面也改成tns。终于,功夫不负有心人,成功了
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://www.139130.net"> <S:Header></S:Header> <S:Body> <tns:GetAccountInfo> <tns:account>XXX</tns:account> <tns:password>YYY</tns:password> </tns:GetAccountInfo> </S:Body> </S:Envelope>
其他的方法参照这个body改
6、postman都通了,一切都迎刃而解,下面附上C#的代码,其他语言的照着发http请求就好了,这个不是难事
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Xml; namespace App.SmsService.Business { class Program { static void Main(string[] args) { try { var responseXml = PostSingle("http://192.168.5.103:8888/Service/WebService.asmx", XXX, YYY, "13800138000", "万般皆下品,小倉唯最高", "", ""); var xmlElement = responseXml.ToXmlElement(); var res = xmlElement.GetInnerText(); if (res != "0") { throw new Exception(GetMtCodeMsg(res)); } } catch (Exception ex) { throw new Exception(ex.Message); } } /// <summary> /// 单发短信并返回Http响应体 /// </summary> /// <param name="url"></param> /// <param name="account"></param> /// <param name="password"></param> /// <param name="mobile"></param> /// <param name="content"></param> /// <param name="phraseIdentify"></param> /// <param name="subid"></param> /// <returns></returns> private static string PostSingle(string url, string account, string password, string mobile, string content, string phraseIdentify, string subid) { var parameters = new Dictionary<string, string> { {"account", account}, {"password", password}, {"mobile", mobile}, {"content", content}, {"phraseIdentify", phraseIdentify}, {"subid", subid} }; var parameterStr = parameters.Select(c => $"<tns:{c.Key}>{c.Value}</tns:{c.Key}>").JoinAsString(" "); var body = $"<S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tns=\"http://www.139130.net\"> <S:Header> </S:Header> <S:Body> <tns:PostSingle> {parameterStr} </tns:PostSingle> </S:Body> </S:Envelope>"; var responseXml = HttpExtension.PostHttp(url, body, ContentType.Xml.ToHeader()); return responseXml; } /// <summary> /// 根据mtCode获取信息 /// </summary> /// <param name="mtCode"></param> /// <returns></returns> private static string GetMtCodeMsg(string mtCode) { return mtCode switch { "0" => "成功", "-1" => "账号无效", "-2" => "参数:无效", "-3" => "连接不上服务器", "-5" => "无效的短信数据,号码格式不对", "-6" => "用户名密码错误", "-7" => "旧密码不正确", "-9" => "资金账户不存在", "-11" => "包号码数量超过最大限制", "-12" => "余额不足", "-13" => "账号没有发送权限", "-14" => "模板校验没有通过", "-99" => "系统内部错误", "-100" => "其它错误", _ => $"未知错误,mtCode:{mtCode}", }; } } public static class HttpExtension { /// <summary> /// 发送Get请求 /// </summary> /// <param name="uri"></param> /// <param name="requestBody"></param> /// <param name="headers"></param> /// <returns></returns> public static string GetHttp(string uri, string requestBody, Dictionary<string, string> headers = null) { return SendHttp(uri, requestBody, "GET", headers); } /// <summary> /// 发送Post请求 /// </summary> /// <param name="uri"></param> /// <param name="requestBody"></param> /// <param name="headers"></param> /// <returns></returns> public static string PostHttp(string uri, string requestBody, Dictionary<string, string> headers = null) { return SendHttp(uri, requestBody, "POST", headers); } /// <summary> /// 发送http报文,并返回响应报文的实体 /// </summary> /// <param name="uri">请求uri</param> /// <param name="requestBody">请求报文的实体</param> /// <param name="method"></param> /// <param name="headers">需要额外设置的请求头</param> /// <returns></returns> private static string SendHttp(string uri, string requestBody, string method = "POST", Dictionary<string, string> headers = null) { var data = Encoding.UTF8.GetBytes(requestBody); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); request.Method = method; //request.ContentType = "application/soap+xml; charset=utf-8"; if (headers != null && headers.Any()) { foreach (var header in headers) { request.Headers.Add(header.Key, header.Value); } } request.Timeout = 30000; request.ContentLength = data.Length; Stream requestStream = request.GetRequestStream(); requestStream.Write(data, 0, data.Length); requestStream.Close(); WebResponse webResponse = request.GetResponse(); StreamReader streamReader = new StreamReader(webResponse.GetResponseStream() ?? throw new InvalidOperationException(), Encoding.UTF8); var responseBody = streamReader.ReadToEnd(); streamReader.Close(); return responseBody; } /// <summary> /// Content-Type转化为Header字典 /// </summary> /// <param name="contentType"></param> /// <param name="charSet"></param> /// <returns></returns> public static Dictionary<string, string> ToHeader(this ContentType contentType, string charSet = "utf-8") { var dic = new Dictionary<string, string> { {"Content-Type", $"{contentType.GetValue<ContentTypeAttribute>()}; charset={charSet}"} }; return dic; } } /// <summary> /// 常见Content-Type /// </summary> public enum ContentType { /// <summary> /// HTML格式 /// </summary> [ContentType("text/html")] Html, /// <summary> /// 纯文本 /// </summary> [ContentType("text/plain")] Plain, /// <summary> /// Xml格式 /// </summary> [ContentType("text/xml")] Xml, /// <summary> /// WebService 1.2用的Xml /// </summary> [ContentType("text/soap+xml")] Soap12, /// <summary> /// Json格式 /// </summary> [ContentType("application/json")] Json, /// <summary> /// 表单中默认的encType,表单数据被编码为key/value格式发送到服务器 /// </summary> [ContentType("application/x-www-form-urlencoded")] FormUrlencoded, /// <summary> /// 表单数据 /// </summary> [ContentType("multipart/form-data")] FormData, /// <summary> /// 二进制流数据 /// </summary> [ContentType("application/octet-stream")] Stream } [AttributeUsage(AttributeTargets.All)] public abstract class AttributeBase : Attribute { protected AttributeBase(string value) { Value = value; } public string Value { get; protected set; } } /// <summary> /// 特性的拓展 /// </summary> public static class AttributeExtension { /// <summary> /// 获取特性值 /// </summary> /// <param name="value"></param> /// <returns></returns> public static string GetValue<TAttributeType>(this Enum value) where TAttributeType : AttributeBase { var str = string.Empty; try { TAttributeType userTypeAttribute = (TAttributeType)((IEnumerable<object>)value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(TAttributeType), false)).FirstOrDefault<object>(); str = userTypeAttribute == null ? value.ToString() : userTypeAttribute.Value; } catch { // ignore } return str; } } public class ContentTypeAttribute : AttributeBase { public ContentTypeAttribute(string value) : base(value) { } } public static class XmlExtension { /// <summary> /// 字符串转XmlElement /// </summary> /// <param name="xml"></param> /// <returns></returns> public static XmlElement ToXmlElement(this string xml) { var xmlDocument = new XmlDocument(); xmlDocument.LoadXml(xml); var root = xmlDocument.DocumentElement; return root; } /// <summary> /// 获取InnerText /// </summary> /// <param name="element"></param> /// <param name="path">形如:./XX/YY</param> /// <returns></returns> public static string GetInnerText(this XmlElement element, string path = ".") { var innerText = element.SelectSingleNode(path)?.InnerText; return innerText; } } }
最后,我再吐个槽,这些个短信厂商、单点登录厂商都根本不会编写文档,又不是什么人都看得懂你们说的话(技术点),但一定所有码农都懂Http,你放一张postman的截图、wireshark的包亦或者是Http报文,都好过你花那么大力气写那么一大坨