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:

 using Catcher.API.Helpers;
namespace Catcher.API
{
/// <summary>
/// the entity of route parameters
/// </summary>
public class UrlParaEntity
{
public string Type { get; set; }
public string PageIndex { get; set; }
public string PageSize { get; set; }
public string Sign { get; set; }
/// <summary>
/// the key
/// </summary>
const string encryptKey = "c1a2t3c4h5e6r.";
/// <summary>
/// validate the parameters
/// </summary>
/// <returns></returns>
public bool Validate()
{
return this.Sign == EncryptHelper.GetEncryptResult((Type + PageIndex + PageSize),encryptKey);
}
}
}
实体里面包含了一个校验的方法来判断参数是否有被篡改。sign参数的加密规则是:把type、pageindex、pagesize三个参数

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

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

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

 using System.Security.Cryptography;
using System.Text;
namespace Catcher.API.Helpers
{
public class EncryptHelper
{
/// <summary>
/// HMACMD5 encrypt
/// </summary>
/// <param name="data">the date to encrypt</param>
/// <param name="key">the key used in HMACMD5</param>
/// <returns></returns>
public static string GetEncryptResult(string data, string key)
{
HMACMD5 source = new HMACMD5(Encoding.UTF8.GetBytes(key));
byte[] buff = source.ComputeHash(Encoding.UTF8.GetBytes(data));
string result = string.Empty;
for (int i = ; i < buff.Length; i++)
{
result += buff[i].ToString("X2"); // hex format
}
return result;
}
}
}

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

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

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

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

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

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

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

 using Nancy;
using Nancy.ModelBinding;
namespace Catcher.API
{
public class BaseOpenAPIModule : NancyModule
{
public BaseOpenAPIModule()
{
}
public BaseOpenAPIModule(string modulePath)
: base(modulePath)
{
Before += TokenValidBefore;
}
/// <summary>
/// validate the parameters in before pipeline
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Response TokenValidBefore(NancyContext context)
{
//to bind the parameters of the route parameters
var para = this.Bind<UrlParaEntity>();
//if pass the validate return null
return !para.Validate() ? Response.AsText("I think you are a bad man!!") : null;
}
}
}

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

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

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

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

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

 using Nancy;
using System.Collections.Generic;
namespace Catcher.API
{
public class OpenProductAPI : BaseOpenAPIModule
{
public OpenProductAPI() : base ("/product")
{
Get["/"] = _ =>
{
var list = new List<Product>()
{
new Product { Id=, Name="p1", Type="t1", Price=12.9m, OtherProp="" },
new Product { Id=, Name="p2", Type="t2", Price=52.9m, OtherProp="remark" }
};
//response the json value
return Response.AsJson(list);
//response the xml value
//return Response.AsXml(list);
};
}
}
}

这里的代码是最简单的,只是单纯的返回数据就是了!不过要注意的是,这个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,并没有涉及哈希等其他类型:

 using StackExchange.Redis;
using System;
using Newtonsoft.Json;
namespace Catcher.API.Helpers
{
public class RedisCacheHelper
{
/// <summary>
/// get the connection string from the config
/// </summary>
private static string _connstr = System.Configuration.ConfigurationManager.AppSettings["redisConnectionString"];
/// <summary>
/// instance of the <see cref="ConnectionMultiplexer"/>
/// </summary>
private static ConnectionMultiplexer _conn = ConnectionMultiplexer.Connect(_connstr);
/// <summary>
/// the database of the redis
/// </summary>
private static IDatabase _db = _conn.GetDatabase();
/// <summary>
/// set the string cache
/// </summary>
/// <param name="key">Key of Redis</param>
/// <param name="value">value of the key</param>
/// <param name="expiry">expiry time</param>
/// <returns>true/false</returns>
public static bool Set(string key, string value, TimeSpan? expiry = default(TimeSpan?))
{
return _db.StringSet(key, value, expiry);
}
/// <summary>
/// set the entity cache
/// </summary>
/// <typeparam name="T">type of the obj</typeparam>
/// <param name="key">key of redis</param>
/// <param name="obj">value of the key</param>
/// <param name="expiry">expiry time</param>
/// <returns>true/false</returns>
public static bool Set<T>(string key, T obj, TimeSpan? expiry = default(TimeSpan?))
{
string json = JsonConvert.SerializeObject(obj);
return _db.StringSet(key, json, expiry);
}
/// <summary>
/// get the value by the redis key
/// </summary>
/// <param name="key">Key of Redis</param>
/// <returns>value of the key</returns>
public static RedisValue Get(string key)
{
return _db.StringGet(key);
}
/// <summary>
/// get the value by the redis key
/// </summary>
/// <typeparam name="T">type of the entity</typeparam>
/// <param name="key">key of redis</param>
/// <returns>entity of the key</returns>
public static T Get<T>(string key)
{
if (!Exist(key))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(_db.StringGet(key));
}
/// <summary>
/// whether the key exist
/// </summary>
/// <param name="key">key of redis</param>
/// <returns>true/false</returns>
public static bool Exist(string key)
{
return _db.KeyExists(key);
}
/// <summary>
/// remove the cache by the key
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool Remove(string key)
{
return _db.KeyDelete(key);
}
}
}

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

 using Nancy;
using Nancy.ModelBinding;
using Catcher.API.Helpers;
using System;
using System.Configuration;
namespace Catcher.API
{
public class BaseOpenAPIModule : NancyModule
{
public BaseOpenAPIModule()
{
}
public BaseOpenAPIModule(string modulePath)
: base(modulePath)
{
Before += TokenValidBefore;
}
/// <summary>
/// validate the parameters in before pipeline
/// </summary>
/// <param name="context">the nancy context</param>
/// <returns></returns>
private Response TokenValidBefore(NancyContext context)
{
string ipAddr = context.Request.UserHostAddress;
if (IsIPUpToLimit(ipAddr))
return Response.AsText("up to the limit"); //to bind the parameters of the route parameters
var para = this.Bind<UrlParaEntity>();
//if pass the validate return null
return !para.Validate() ? Response.AsText("I think you are a bad man!!") : null;
}
/// <summary>
/// whether the ip address up to the limited count
/// </summary>
/// <param name="ipAddr">the ip address</param>
/// <returns>true/false</returns>
private bool IsIPUpToLimit(string ipAddr)
{
bool flag = false;
//end of the day
DateTime endTime = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd 23:59:59"));
TimeSpan seconds = endTime - DateTime.Now;
//first or not
if (RedisCacheHelper.Exist(ipAddr))
{
int count = (int)RedisCacheHelper.Get(ipAddr);
if (count < int.Parse(ConfigurationManager.AppSettings["limitCount"].ToString()))
RedisCacheHelper.Set(ipAddr, count + , TimeSpan.FromSeconds(seconds.TotalSeconds));
else
flag = true;
}
else
{
RedisCacheHelper.Set(ipAddr, , TimeSpan.FromSeconds(seconds.TotalSeconds));
}
return flag;
}
}
}

这里添加了一个方法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. 我这么玩Web Api(二):数据验证,全局数据验证与单元测试

    目录 一.模型状态 - ModelState 二.数据注解 - Data Annotations 三.自定义数据注解 四.全局数据验证 五.单元测试   一.模型状态 - ModelState 我理解 ...

  2. 【腾讯Bugly干货分享】Android Linker 与 SO 加壳技术

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57e3a3bc42eb88da6d4be143 作者:王赛 1. 前言 Andr ...

  3. WebApi返回Json格式字符串

    WebApi返回json格式字符串, 在网上能找到好几种方法, 其中有三种普遍的方法, 但是感觉都不怎么好. 先贴一下, 网上给的常用方法吧. 方法一:(改配置法) 找到Global.asax文件,在 ...

  4. php 基础代码大全(不断完善中)

    下面是基础的PHP的代码,不断完善中~ //语法错误(syntax error)在语法分析阶段,源代码并未被执行,故不会有任何输出. /* [命名规则] */ 常量名 类常量建议全大写,单词间用下划线 ...

  5. Android中使用ViewFlipper实现屏幕页面切换(关于坐标轴的问题已补充更改)

    屏幕切换指的是在同一个Activity内屏幕间的切换,ViewFlipper继承了Framelayout类,ViewAnimator类的作用是为FrameLayout里面的View切换提供动画效果.如 ...

  6. Autofac 的点滴

    泛型类型的注册和使用 public interface IRepository<T> where T:class { } public interface ISchoolDetailRep ...

  7. MonoDevelop 4.0.9 on CentOS 6.3 安装笔记

    前言 Mono的前东家Novell公司旗下的SUSE Linux系列对Mono及MonoDevelop提供内置支持,所以在SUSE/OpenSUSE这些Linux系统中安装MonoDevelop是非常 ...

  8. NoSql1 在Linux(CentOS)上安装memcached及使用

    前言:       今天是初五,生活基本要从过年的节奏中回归到正常的生活了,所以想想也该想想与工作有关的事情了.我之前在工作中会经常使用memcached和redis,但是自己一直没有时间系统的好好看 ...

  9. Fedora 21 安装 Nvidia 驱动以及失败后的补救方法

    在 Linux 桌面系统下玩了这么久,大部分时间都是使用 Ubuntu,偶尔使用一下 Fedora.我的电脑中安装有多个 Linux 发行版,见这里<在同一个硬盘上安装多个Linux发行版及Fe ...

  10. 2000条你应知的WPF小姿势 基础篇<51-56 依赖属性>

    前一阵子由于个人生活原因,具体见上一篇,耽搁了一阵子,在这里也十分感谢大家支持和鼓励.现在开始继续做WPF2000系列. 在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件 ...