0x01、前言

现阶段,用来实现API的可能大部分用的是ASP.NET Web API或者是ASP.NET MVC,毕竟是微软官方出产的,用的人也多。

但是呢,NancyFx也是一个很不错的选择。毕竟人家的官方文档都是这样写的:framework for building HTTP based services。

本文主要是通过一个简单的场景和简单的实现来说明。

0x02、场景假设与分析

现在A公司与B公司有一些业务上的合作,B公司要得到一些关于A公司产品的信息

所以,A公司需要提供一个简单的接口去给B公司调用,从而获得公司的产品信息。

那么,问题来了,这是A公司提供的一个对外接口,那这个接口是任何人都可以访问吗?

是可以无限制的访问吗?有人闲着没事一直访问这个接口怎么办?

很明显,这个接口是A公司专门提供给B公司用的,所以要想方设法禁止其他人访问,不然A公司的信息就要。。。

当然,像这种类型的接口,常规的做法基本上就是用签名去检验传递的参数是否被篡改过。

比如像这样一个api

http:api.example.com/getall?p1=a&p2=b&sign=sign

带了三个参数,p1,p2,sign,其中sign这个值是由p1和p2来决定的

可以是这两个参数拼接在一起,再经过某种加密得到的一个值

也可以是这两个参数加上一个双方约定的私钥,再经过某种加密得到的一个值

也可以是增加一个时间戳得到三个参数再加上双方约定的私钥,经过某种加密得到的一个值

也可以是在时间戳的基础上加一个随机数得到四个参数再加上双方约定的私钥,经过某种加密得到的一个值

本文采取的是第二种,加一个双方的私钥。至于加时间戳和随机数也是同样的道理。

现在A、B公司约定他们的私钥为:c1a2t3c4h5e6r.

并且B公司向A公司发出请求带的参数有:

type            ----产品类型
pageindex ----页码
pagesize    ----每页显示多少产品
sign         ----至关重要的签名参数

通过这些参数,B公司就可以得到一些A公司的产品信息了

这就就意味着 B公司请求数据的地址就是 :

http://api.a-company.com/getproduct?type=xxx&pageindex=xx&pagesize=xxx&sign=xxx

一般情况下,两个公司商讨完毕后就会产生一份详细的API文档

这份文档会包含请求的每个参数的要求,如长度限制、加密方法、如何加密等,以及返回的数据格式等等

这个时候,A公司就会照着这份文档进行开发。

下面就是设计开发阶段了

0x03、设计与实现

既然已经知道了要传输的参数,那么就先建立一个路由的参数实体UrlParaEntity:

  1. using Catcher.API.Helpers;
  2. namespace Catcher.API
  3. {
  4. /// <summary>
  5. /// the entity of route parameters
  6. /// </summary>
  7. public class UrlParaEntity
  8. {
  9. public string Type { get; set; }
  10. public string PageIndex { get; set; }
  11. public string PageSize { get; set; }
  12. public string Sign { get; set; }
  13. /// <summary>
  14. /// the key
  15. /// </summary>
  16. const string encryptKey = "c1a2t3c4h5e6r.";
  17. /// <summary>
  18. /// validate the parameters
  19. /// </summary>
  20. /// <returns></returns>
  21. public bool Validate()
  22. {
  23. return this.Sign == EncryptHelper.GetEncryptResult((Type + PageIndex + PageSize),encryptKey);
  24. }
  25. }
  26. }
实体里面包含了一个校验的方法来判断参数是否有被篡改。sign参数的加密规则是:把type、pageindex、pagesize三个参数

拼接起来,并加上私钥来加密。这里为了偷懒,私钥直接在代码里了写死了。正常情况下应该将私钥存放在数据库中的,有一个key与之对应。

下面就是A、B公司协商好的加密算法了。

这里采用的加密算法是:HMACMD5 ,它所在的命名空间是system.security.cryptography

  1. using System.Security.Cryptography;
  2. using System.Text;
  3. namespace Catcher.API.Helpers
  4. {
  5. public class EncryptHelper
  6. {
  7. /// <summary>
  8. /// HMACMD5 encrypt
  9. /// </summary>
  10. /// <param name="data">the date to encrypt</param>
  11. /// <param name="key">the key used in HMACMD5</param>
  12. /// <returns></returns>
  13. public static string GetEncryptResult(string data, string key)
  14. {
  15. HMACMD5 source = new HMACMD5(Encoding.UTF8.GetBytes(key));
  16. byte[] buff = source.ComputeHash(Encoding.UTF8.GetBytes(data));
  17. string result = string.Empty;
  18. for (int i = ; i < buff.Length; i++)
  19. {
  20. result += buff[i].ToString("X2"); // hex format
  21. }
  22. return result;
  23. }
  24. }
  25. }

基本的东西已经有了,下面就是要怎么去开发API了。

既然前面提到了要校验,那么,我们在那里做校验呢?

是在方法里面做校验吗?这个太不灵活,可能后面会改的很痛苦。DRY嘛,还是要遵守一下的。

用过mvc都会知道,验证某个用户是否有权限访问某页面,常规的做法就是用authorizeattribute

在Nancy中,我是在BeforePipeline中来实现这个校验。

BeforePipeline是什么呢,可以说和mvc中的那个application_beginrequest方法类似!

稍微具体一点的可以看看我之前的博客 (Nancy之Pipelines三兄弟(Before After OnError))。

  1. using Nancy;
  2. using Nancy.ModelBinding;
  3. namespace Catcher.API
  4. {
  5. public class BaseOpenAPIModule : NancyModule
  6. {
  7. public BaseOpenAPIModule()
  8. {
  9. }
  10. public BaseOpenAPIModule(string modulePath)
  11. : base(modulePath)
  12. {
  13. Before += TokenValidBefore;
  14. }
  15. /// <summary>
  16. /// validate the parameters in before pipeline
  17. /// </summary>
  18. /// <param name="context"></param>
  19. /// <returns></returns>
  20. private Response TokenValidBefore(NancyContext context)
  21. {
  22. //to bind the parameters of the route parameters
  23. var para = this.Bind<UrlParaEntity>();
  24. //if pass the validate return null
  25. return !para.Validate() ? Response.AsText("I think you are a bad man!!") : null;
  26. }
  27. }
  28. }

要注意的是这个类要继承NancyModule,这个是根!!就像在MVC中,每一个控制器都要继承Controller一样!

其中的TokenValidBefore方法是关键,通过得到参数实体,调用实体的校验方法去判断,通过就返回null,不通过就给一个提示信息。

这里还是比较简单的做法。适合的场景是仅仅提供少量的接口方法。因为方法一多,不能确保传输的参数名称一致,

那么在bind的时候就会出问题。当然为不同的接口提供一个实体,也是一个不为过的方法。

下面就是Module中的返回数据了。

  1. using Nancy;
  2. using System.Collections.Generic;
  3. namespace Catcher.API
  4. {
  5. public class OpenProductAPI : BaseOpenAPIModule
  6. {
  7. public OpenProductAPI() : base ("/product")
  8. {
  9. Get["/"] = _ =>
  10. {
  11. var list = new List<Product>()
  12. {
  13. new Product { Id=, Name="p1", Type="t1", Price=12.9m, OtherProp="" },
  14. new Product { Id=, Name="p2", Type="t2", Price=52.9m, OtherProp="remark" }
  15. };
  16. //response the json value
  17. return Response.AsJson(list);
  18. //response the xml value
  19. //return Response.AsXml(list);
  20. };
  21. }
  22. }
  23. }

这里的代码是最简单的,只是单纯的返回数据就是了!不过要注意的是,这个Module并不是继承NancyModule

而是继承我们自己定义的BaseOpenAPIModule。

现在返回的数据格式主要有两种,JSON和XML,ASP.NET Web API 和 WCF也可以返回这两种格式的数据。

现在大部分应该是以JSON为主,所以示例也就用了Json,返回xml的写法也在注释中有提到。

到这里,这个简单的接口已经能够正常运行了,下面来看看效果吧:

正确无误的访问链接如下:

http://localhost:62933/product?type=type&pageindex=1&pagesize=2&sign=99186B4B5E923B4631B3E5DAC4134C4D

我们修改pagesize为3在访问就会有问题了!因为sign值是通过前面的三个参数生成的,改动之后,肯定是得不到想到的数据!

所以这就有效的预防了其他人窃取api返回的数据。

到这里,A公司的提出了个问题,这个接口在一天内是不是能够无限次访问?

of course not!!每天一个ip访问1000次都算多了吧!

那么,要如何来限制这个访问频率呢?

首先,要限制ip的访问次数,肯定要存储对应的ip的访问次数,这个毋庸置疑。

其次,每天都有一个上限,有个过期时间。

那么要怎么存储?用什么存储?这又是个问题!!

存数据库吧,用什么数据库呢?SQL Server ? MySql ? MongoDB ? Redis ?

好吧,我选 Redis 。key-value型数据库,再加上可以设置过期的时间,是比较符合我们的这个场景的。

演示这里的频率以天为单位,访问上限次数为10次(设的太多,我怕我的F5键要烂~~)

下面是具体的实现:

首先对Redis的操作简单封装一下,这里的封装只是针对string,并没有涉及哈希等其他类型:

  1. using StackExchange.Redis;
  2. using System;
  3. using Newtonsoft.Json;
  4. namespace Catcher.API.Helpers
  5. {
  6. public class RedisCacheHelper
  7. {
  8. /// <summary>
  9. /// get the connection string from the config
  10. /// </summary>
  11. private static string _connstr = System.Configuration.ConfigurationManager.AppSettings["redisConnectionString"];
  12. /// <summary>
  13. /// instance of the <see cref="ConnectionMultiplexer"/>
  14. /// </summary>
  15. private static ConnectionMultiplexer _conn = ConnectionMultiplexer.Connect(_connstr);
  16. /// <summary>
  17. /// the database of the redis
  18. /// </summary>
  19. private static IDatabase _db = _conn.GetDatabase();
  20. /// <summary>
  21. /// set the string cache
  22. /// </summary>
  23. /// <param name="key">Key of Redis</param>
  24. /// <param name="value">value of the key</param>
  25. /// <param name="expiry">expiry time</param>
  26. /// <returns>true/false</returns>
  27. public static bool Set(string key, string value, TimeSpan? expiry = default(TimeSpan?))
  28. {
  29. return _db.StringSet(key, value, expiry);
  30. }
  31. /// <summary>
  32. /// set the entity cache
  33. /// </summary>
  34. /// <typeparam name="T">type of the obj</typeparam>
  35. /// <param name="key">key of redis</param>
  36. /// <param name="obj">value of the key</param>
  37. /// <param name="expiry">expiry time</param>
  38. /// <returns>true/false</returns>
  39. public static bool Set<T>(string key, T obj, TimeSpan? expiry = default(TimeSpan?))
  40. {
  41. string json = JsonConvert.SerializeObject(obj);
  42. return _db.StringSet(key, json, expiry);
  43. }
  44. /// <summary>
  45. /// get the value by the redis key
  46. /// </summary>
  47. /// <param name="key">Key of Redis</param>
  48. /// <returns>value of the key</returns>
  49. public static RedisValue Get(string key)
  50. {
  51. return _db.StringGet(key);
  52. }
  53. /// <summary>
  54. /// get the value by the redis key
  55. /// </summary>
  56. /// <typeparam name="T">type of the entity</typeparam>
  57. /// <param name="key">key of redis</param>
  58. /// <returns>entity of the key</returns>
  59. public static T Get<T>(string key)
  60. {
  61. if (!Exist(key))
  62. {
  63. return default(T);
  64. }
  65. return JsonConvert.DeserializeObject<T>(_db.StringGet(key));
  66. }
  67. /// <summary>
  68. /// whether the key exist
  69. /// </summary>
  70. /// <param name="key">key of redis</param>
  71. /// <returns>true/false</returns>
  72. public static bool Exist(string key)
  73. {
  74. return _db.KeyExists(key);
  75. }
  76. /// <summary>
  77. /// remove the cache by the key
  78. /// </summary>
  79. /// <param name="key"></param>
  80. /// <returns></returns>
  81. public static bool Remove(string key)
  82. {
  83. return _db.KeyDelete(key);
  84. }
  85. }
  86. }

然后就是修改我们的BaseOpenAPIModule,把这个次数限制加上去。修改过后的代码如下:

  1. using Nancy;
  2. using Nancy.ModelBinding;
  3. using Catcher.API.Helpers;
  4. using System;
  5. using System.Configuration;
  6. namespace Catcher.API
  7. {
  8. public class BaseOpenAPIModule : NancyModule
  9. {
  10. public BaseOpenAPIModule()
  11. {
  12. }
  13. public BaseOpenAPIModule(string modulePath)
  14. : base(modulePath)
  15. {
  16. Before += TokenValidBefore;
  17. }
  18. /// <summary>
  19. /// validate the parameters in before pipeline
  20. /// </summary>
  21. /// <param name="context">the nancy context</param>
  22. /// <returns></returns>
  23. private Response TokenValidBefore(NancyContext context)
  24. {
  25. string ipAddr = context.Request.UserHostAddress;
  26. if (IsIPUpToLimit(ipAddr))
  27. return Response.AsText("up to the limit");
  28.  
  29. //to bind the parameters of the route parameters
  30. var para = this.Bind<UrlParaEntity>();
  31. //if pass the validate return null
  32. return !para.Validate() ? Response.AsText("I think you are a bad man!!") : null;
  33. }
  34. /// <summary>
  35. /// whether the ip address up to the limited count
  36. /// </summary>
  37. /// <param name="ipAddr">the ip address</param>
  38. /// <returns>true/false</returns>
  39. private bool IsIPUpToLimit(string ipAddr)
  40. {
  41. bool flag = false;
  42. //end of the day
  43. DateTime endTime = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd 23:59:59"));
  44. TimeSpan seconds = endTime - DateTime.Now;
  45. //first or not
  46. if (RedisCacheHelper.Exist(ipAddr))
  47. {
  48. int count = (int)RedisCacheHelper.Get(ipAddr);
  49. if (count < int.Parse(ConfigurationManager.AppSettings["limitCount"].ToString()))
  50. RedisCacheHelper.Set(ipAddr, count + , TimeSpan.FromSeconds(seconds.TotalSeconds));
  51. else
  52. flag = true;
  53. }
  54. else
  55. {
  56. RedisCacheHelper.Set(ipAddr, , TimeSpan.FromSeconds(seconds.TotalSeconds));
  57. }
  58. return flag;
  59. }
  60. }
  61. }

这里添加了一个方法IsIPUpToLimit,这个方法通过从Redis中读取ip对应的值,并根据这个值来判断是否超过了上限。

这里的上限次数和redis的连接字符串都放在了appsettings里面,便于修改。

然后在TokenValidBefore方法中获取IP并做次数上限的判断。

下面是效果图

毕竟是要用的,不能在本地调试过了就结束了,还要上线的,说不定上线就会遇到问题的。

下面就结合TinyFox独立版在CentOS7上简单部署一下。

首先要在CentOS7上安装一下redis,具体的安装方法就不在这里说明了(下载源码,编译一下就可以了)。

启动之后如下(这里我换了个端口,没有用默认的):

然后将项目的配置文件的内容copy到tinyfox的配置文件中,这里主要是appsettings里面的redis连接字符串和上限次数

所以只需要把appsettings的内容贴过去就好了。

然后是简单的操作和效果图:

需要注意的是,StackExchange.Redis在mono上面是跑不起来的!

它会提示不能连接到Redis!!这真是不能忍。

不过我能跑起来就肯定有解决的方法啦~~StackExchange.Redis.Mono是可以在mono上跑的版本!!

而且只需要替换掉程序集就可以正常跑起来了。因为这个与StackExchange.Redis的程序集名称是一样的,所以不需要做其他的修改。还是很方便的

 这里需要说明的是,在本地调试的时候,用的redis是windows版的,发布的时候才是用的linux版。

0x04、小结

在这个过程中,也是遇到了一些问题和疑惑。

问题的话主要就是windows独立版的tinyfox调试不成功,只能切换回通用版。

疑惑的话主要就是用Redis做这个次数的限制,是临时想的,不知道是否合理。

Web API   有一个开源的库,里面有这个对次数限制的拓展,有兴趣的可以看看

https://github.com/WebApiContrib/WebAPIContrib/tree/master/src/WebApiContrib

它里面用ConcurrentDictionary来实现了轻量级的缓存。

可能有人会问,ASP.NET MVC 、 ASP.NET Web API 、 NancyFx 之间是什么关系

下面说说我个人的看法(理解不一定正确,望指正):

MVC 很明显 包含了 M 、V、 C这三个部分

Web API 可以说是只包含了 M 、 C这两个部分

这里的话可以说Web API 是 MVC的一个子集,

所以说,web api能做的,mvc也能做,所以有许多公司是直接用mvc来开发接口的

NancyFx与Web API的话,并没有太大的关系

Web API 可以很容易的构建HTTP services,也是基于RESTful的

NancyFx 是基于HTTP的轻量级框架,也可以构建RESTful API。

硬要说有关系的话,那就是HTTP和RESTful。

NancyFx与MVC的话,也是没有太大的关系

但他们能算的上是两个好朋友,有着共同的兴趣爱好,能完成同样的事情

 

API,实现的方式有很多,怎么选择就看个人的想法了。

更多有关NancyFx的文章,可以移步到Nancy之大杂绘

Nancy之实现API的功能的更多相关文章

  1. Nancy之实现API

    Nancy之实现API的功能 0x01.前言 现阶段,用来实现API的可能大部分用的是ASP.NET Web API或者是ASP.NET MVC,毕竟是微软官方出产的,用的人也多. 但是呢,Nancy ...

  2. 浅析如何在Nancy中生成API文档

    前言 前后端分离,或许是现如今最为流行开发方式,包括UWP.Android和IOS这样的手机客户端都是需要调用后台的API来进行数据的交互. 但是这样对前端开发和APP开发就会面临这样一个问题:如何知 ...

  3. 对TControl和TWinControl相同与不同之处的深刻理解(每一个WinControl就相当于扮演了整个Windows的窗口管理角色,主要是窗口显示和窗口大小)——TWinControl就两个作用(管理子控件的功能和调用句柄API的功能)

    TControl是图形控件,它本身没有句柄,所以不能直接使用WINAPI显示,调整位置,发消息等等,只能想办法间接取得想要的效果,但是可以直接使用一些不需要句柄的API,比如InvalidateRec ...

  4. Windows加密API的功能分类

    本地数据加密保护本地数据加密保护机制提供了简单的DAPI调用接口,密钥管理等等一概由系统来处理.DAPI的数据加密保护机制在用户登录会话范围或者本地计算范围,使用操作系统设计的方式加密保护数据和解密还 ...

  5. 即将到来的Autodesk 主要产品2015版 产品和API新功能在线培训(免费)

    一年一度的Autodesk主要产品和API在线培训课程在5月份即将開始.我们呈献给大家5个课程. 1. Revit 2015 产品新功能及API 概览 2. Vault 2015产品新功能及API 概 ...

  6. eclipse(STS)安装jd-eclipse插件实现查看API源代码功能

    emmm,IDEA确实是比STS智能很多,不过适当的转化也是需要的,这里介绍一下eclipse(STS)实现查看class反编译的源文件的功能 去Java Decompiler官网下一下eclipse ...

  7. 13 Tensorflow API主要功能

    要想使用Tensorflow API,首先要知道它能干什么.Tensorflow具有Python.C++.Java.Go等多种语言API,其中Python的API是最简单和好用的. Tensor Tr ...

  8. 调用百度翻译API接口功能

    public string appid = "自己的APPID"; public string q = "要翻译的文本"; "; public str ...

  9. asp.net web api 授权功能

    1.重写授权方法 using System; using System.Collections.Generic; using System.Linq; using System.Net; using ...

随机推荐

  1. jQuery学习之路(7)- 用原生JavaScript实现jQuery的某些简单功能

    ▓▓▓▓▓▓ 大致介绍 学习了妙味,用原生的JavaScript实现jQuery中的某些部分功能 定义自己的函数库lQuery ▓▓▓▓▓▓ $()选择器的实现 jQuery是面向对象的,所以自己编写 ...

  2. MSYS2环境下编译X265

    HEVC(High Efficiency Video Coding),是一种新的视频压缩标准.可以替代H.264/ AVC编码,使得保持相同质量的情况下,体积减少40%左右.目前有多种实现版本,x26 ...

  3. Maven 整合FreeMarker使用

    pom.xml <!-- freemarker jar --> <dependency> <groupId>org.freemarker</groupId&g ...

  4. jQuery可自动播放动画焦点图插件Koala

    Koala是一款简单而实用的jQuery焦点图幻灯片插件,焦点图不仅可以在播放图片的时候让图片有淡入淡出的动画效果,而且图片可以自动播放.该jQuery焦点图的每一张图片都可以设置文字描述,并浮动在图 ...

  5. css_02之盒模型、渐变

    1.框模型:盒模型,①对象实际宽度=左右外边距+左右边框+左右内边距 + width:②对象实际高度=上下外边距+上下边框+上下内边距 + height: 2.外边距:margin:取值:①top(上 ...

  6. Linux常用命令

    命令格式与目录处理命令 ls 命令格式与目录处理命令 ls 命令格式:命令 [-选项][参数] 例:ls -la /etc 说明: 1)个别命令使用不遵循格式 2)当有多个选项时,可以写在一起 3)简 ...

  7. 项目游戏开发日记 No.0x000001

    14软二杨近星(2014551622) 既然已经决定了开发软件, 时不时就要练练手, 还要时不时的去寻找素材, 因为开发的人物设定就是DotA2里面的祈求者, 所以, 就去找了他的相关人物图片和模型, ...

  8. java根据html生成摘要

    转自:http://java.freesion.com/article/48772295755/ 开发一个系统,需要用到这个,根据html生成你指定多少位的摘要 package com.chendao ...

  9. 一键部署mono 免费空间支持ASP.NET MVC 再也不担心伙食费换空间了

    一直以来 部署mono 都是很头疼的事情 因为是我在是不熟悉非win环境,今天偶然发现这个项目,挺好的,分享下 https://github.com/wshearn/openshift-communi ...

  10. .NET Web的身份认证

    百度一下”asp.net身份认证“,你会得到很多相关的资料,这些资料通常上来就会介绍诸如”Form认证“”Windows认证“等内容,而没有给出一个完整的流程.初学者对此往往一头雾水,我也曾经被坑过很 ...