Nancy's ability to implement the API 0x01, preface
At this stage, most of the possibilities used to implement the API are ASP. NET Web API or ASP. NET MVC, after all, Microsoft's official production, with more people.
But, Nancyfx is also a very good choice. After all, the official documents are written in this way: the framework for building HTTP based services.
This article is mainly illustrated by a simple scenario and a simple implementation.
0x02, scenario hypothesis and analysis
Now a company and B Company have some business cooperation, B Company to get some information about a company's products
Therefore, a company needs to provide a simple interface to the B company to call, so as to obtain the company's product information.
So, the question is, is this an external interface provided by company A, and is this interface accessible to anyone?
is unrestricted access possible? Someone's idle. Always access this interface what to do?
Obviously, this interface is a company dedicated to the company B, so to find ways to prohibit other people access, otherwise a company's information will be ...
Of course, like this type of interface, the general practice is basically to use the signature to verify whether the passed parameters have been tampered with.
such as an API like this
Http:api.example.com/getall?p1=a&p2=b&sign=sign
With three parameters, P1,p2,sign, where sign is determined by P1 and P2.
Can be a value that the two parameters are stitched together and then obtained by some kind of encryption
It can also be the two parameters plus a mutually agreed private key, followed by some kind of encryption to get a value
It can also be added a timestamp to get three parameters plus the private key agreed by both parties, a value obtained through some kind of encryption
You can also add a random number on the basis of a timestamp to get four parameters plus the private key agreed by both parties, a value obtained through some kind of encryption
This article takes the second, plus a two-way private key. Adding timestamps and random numbers is the same thing.
Now A, B company agreed that their private key is: c1a2t3c4h5e6r.
and B Company to a company issued a request belt parameters are:
Type----Product Type pageindex----page pagesize----How many products are displayed per page sign----key signature parameters
With these parameters, Company B will be able to get some product information for company A.
This means that company B's request data address is:
Http://api.a-company.com/getproduct?type=xxx&pageindex=xx&pagesize=xxx&sign=xxx
In general, after the two companies have negotiated, a detailed API document will be generated
This document will contain the requirements for each parameter requested, such as length limit, encryption method, how to encrypt, etc., and the data format returned, etc.
At this time, company A will follow this document for development.
Here's the design and development phase.
0X03, design and implementation
Now that you know the parameters to transfer, first establish a routed parameter entity 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;} Ten public string PageIndex {get; set;} One public string PageSize {get; set;} Public string Sign {get; set;} <SUMMARY>14/// the KEY15// </summary>16 const string Encryptkey = " c1a2t3c4h5e6r. "; <SUMMARY>18///Validate the PARAMETERS19//</summary>20// <returns ></RETURNS>21 Public bool Validate () . Sign = = Encrypthelper.getencryptresult ((Type + PageIndex + PageSize), encryptkey); }26}
The entity contains a checksum method to determine if the parameter has been tampered with. The encryption rules for the sign parameter are: Three parameters of type, pageindex, pagesize
Together, and add the private key to encrypt. Here in order to lazy, the private key directly in the code to write dead. Under normal circumstances, the private key should be stored in the database, and there is a key corresponding to it.
The following is a, B company negotiated encryption algorithm.
The cryptographic algorithm used here is: HMACMD5, where the namespace is 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 E Ncrypt 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), { HMACMD5 Source = new HMACMD5 ( Encoding.UTF8.GetBytes (key)); byte[] buff = source. ComputeHash (data); Encoding.UTF8.GetBytes string result = String. Empty;18 for (int i = 0; i < buff. Length; i++) ( + + result + buff[i]. ToString ("X2"); Hex format21 }22 return result;23 }24 }25}
Basic things already have, here is how to develop the API.
Since the previous reference is to verify, then where do we do the calibration?
Do you check it in the method? This is too inflexible, it may be painful to change back. Dry, still have to abide by.
Using MVC will know that verifying a user has access to a page, the general practice is to use Authorizeattribute
In Nancy, I was in beforepipeline to implement this check.
What is Beforepipeline, which can be said to be similar to the Application_BeginRequest method in MVC!
A little more specific can look at my previous blog (Nancy's Pipelines three brothers (before after OnError).
1 using Nancy; 2 using Nancy.modelbinding; 3 namespace Catcher.api 4 {5 public class Baseopenapimodule:nancym Odule 6 {7 public baseopenapimodule () 8 {9}10 public baseopenapimodule (string Modulepath) 11:base (Modulepath) {before + = Tokenvalidbefore; }15//<summary>16//Validate the parameters in before PIPELINE17//</sum MARY>18//<param name= "context" ></param>19//<returns></returns>20 Private Response Tokenvalidbefore (Nancycontext context) {//to Bind the parameters of the route PA RAMETERS23 var para = this. Bind<urlparaentity> (),//if pass the Validate return NULL25 return!para. Validate ()? Response.astext ("I think You is a bad man!!"): null; }27}28}
Note that this class is to inherit Nancymodule, this is the root!! Just like in MVC, every controller has to inherit controllers!
The Tokenvalidbefore method is the key, by getting the parameter entity, call the entity's check method to judge, through the return null, do not pass on to give a hint message.
This is still a relatively simple practice. The appropriate scenario is to provide only a small number of interface methods. Because of the number of methods, it is not possible to ensure that the parameter names are identical
Then there will be a problem with bind. Of course, providing an entity for different interfaces is also a non-excessive method.
The following is the return data in the 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["/"] = _ = = Ten {one var list = new List<product> () {new P roduct {id=1, name= "P1", type= "T1", Price=12.9m, otherprop= ""},14 new Product {id=2, name= "P2", type= "T2", price=52 .9m, otherprop= "remark"} };16 //response the JSON value17 return Response.asjson (list); 18 //response the XML value19 //return response.asxml (list); };21}22 }23}
The code here is the simplest, just the simple return data is! However, it is important to note that this module does not inherit Nancymodule
But to inherit our own definition of baseopenapimodule.
There are two main data formats returned, and JSON and Xml,asp.net Web APIs and WCF can also return data in both formats.
Now most of it should be JSON-based, as the example also uses JSON, the return of the XML notation is also mentioned in the comments.
Here, this simple interface has been able to run normally, the following look at the effect:
The correct access links are as follows:
http://localhost:62933/product?type=type&pageindex=1&pagesize=2&sign= 99186b4b5e923b4631b3e5dac4134c4d
We modified pagesize to 3 in the access will have a problem! Because the sign value is generated by the previous three parameters, after the change, it is definitely not get the thought of the data!
So this effectively prevents other people from stealing the data returned by the API.
Here, a company has raised a question, this interface in a day is not able to access unlimited?
Of course not!! One IP access 1000 times a day is too much!
So how do you limit this frequency of access?
First of all, to limit the number of IP access, it is sure to store the corresponding IP access times, this is beyond doubt.
Second, there is an upper limit every day, and there is an expiration time.
So how do you store it? With what storage? This is a problem!!
Save the database, what database do you use? SQL Server? Mysql? Mongodb? Redis?
Okay, I'll choose Redis. The Key-value database, plus the time it can be set to expire, is more in line with our scenario.
Demo here frequency in days, access to the maximum number of times is 10 (set too much, I am afraid my F5 key to rot ~ ~)
Here are the specific implementations:
First, the operation of Redis is simply encapsulated, where the encapsulation is only for string and does not involve other types such as hashing:
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 CONFIG10//</summary>11 private static string _connstr = Syst Em. configuration.configurationmanager.appsettings["redisconnectionstring"];12///<summary>13//Insta nCE of the <see cref= "connectionmultiplexer"/>14//</summary>15 private static Connectionmul Tiplexer _conn = Connectionmultiplexer.connect (_CONNSTR);//<summary>17//The database of the REDIS18//</summary>19 private static Idatabase _db = _conn. Getdatabase ();///<SUMMARY>21//Set the string Cache22//</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& lt;/returns>27 public static bool Set (string key, String value, TimeSpan? expiry = Default (TimeSpan)) {_db return. Stringset (key, value, expiry);}31///<SUMMARY>32//Set the entity Cache33// </SUMMARY>34//<typeparam name= "T" >type of the obj</typeparam>35//<param name= "K EY ">key of redis</param>36///<param name=" obj ">value of the key</param>37//<p Aram name= "Expiry" >expiry time</param>38//<returns>true/false</returns>39 public s tatic bool Set<t> (string key, T obj, TimeSpan? expiry = Default (TimeSpan)) The string json = Jsonconvert.serializeobject (obj), and the _db return. Stringset (key, JSON, expiry);}44//<SUMMARY>45//Get the value by the Redis key46///</summary>47///<param name= "Ke Y ">key of redis</param>48//<returns>value of the key</returns>49 public static Red Isvalue Get (string key) {_db return. Stringget (key);}53//<summary>54//Get the value by the Redis key55///</ 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 G Et<t> (String key) if (! Exist (key)) (+ +) (T),}65 return jsonconvert.deserial Izeobject<t> (_db. Stringget (key));}67//<summary>68//whether the key exist69//</summary& Gt;70 <param name= "Key" >key of redis</param>71//<returns>true/false</returns>72 public static bool Exist (string key), {_db return. Keyexists (key);}76//<summary>77///Remove the cache by the key78///</sum mary>79//<param name= "key" ></param>80//<returns></returns>81 Publ IC static bool Remove (string key) _db. Keydelete (key); 84}85}86}
Then we modify our baseopenapimodule and add this limit to the number of times. The modified code is as follows:
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 {ten public baseopenapimodule ( ) {}13 public baseopenapimodule (String modulepath) 14:base (Modulepath) 15 {before + = Tokenvalidbefore; }18//<summary>19//Validate the parameters in before PIPELINE20//</sum MARY>21//<param name= "context" >the Nancy Context</param>22//<returns></retur ns>23 private Response Tokenvalidbefore (Nancycontext context), {string ipaddr = Context . request.userhostaddress;26 if (Isipuptolimit (ipaddr)) return Response.astext ("Up To the Limit ");//to bind the ParaMeters of the route PARAMETERS30 var para = this. Bind<urlparaentity> ();//if pass the Validate return null32 return!para. Validate ()? Response.astext ("I think You is a bad man!!"): null; }34//<summary>35//whether the IP address up to the limited count36//< /summary>37//<param name= "ipaddr" >the IP address</param>38//<returns>true/false </returns>39 private bool Isipuptolimit (string ipaddr) + {$ BOOL flag = false;42 End of the day43 DateTime endTime = DateTime.Parse (DateTime.Now.ToString ("Yyyy-mm-dd 23:59:59"); The TimeSpan seconds = endtime-datetime.now;45//first or not46 if (Rediscach Ehelper.exist (IPADDR)) ({) = (int) count = (int.) rediscachehelper.get (IPADDR); 49 if (count < int. Parse (Configurationmanager.appsettings["Limitcount"]. ToString ())) Rediscachehelper.set (ipaddr, Count + 1, timespan.fromseconds (seconds. totalseconds)); else52 flag = true; }54 Else55 {rediscachehelper.set (ipaddr, 1, timespan.fromseconds (seconds. totalseconds));}58 return flag;59}60}61}
Here is a method isipuptolimit, which reads the IP corresponding value from Redis and determines whether the upper limit is exceeded by this value.
The maximum number of times and the Redis connection string are placed inside the appsettings for easy modification.
Then the IP is obtained in the Tokenvalidbefore method and the maximum number of times is determined.
Below is
After all, is to use, can not be debugged at the end of the local, but also on-line, perhaps on-line will encounter problems.
The following is a simple deployment on the CentOS7 with the Tinyfox standalone version.
First to install the Redis on the CentOS7, the specific installation method is not here to explain (download the source code, compile a bit).
After startup (I changed a port here, not the default):
Then copy the contents of the project's configuration file into the Tinyfox configuration file, which is mainly the Redis connection string and the maximum number of appsettings
So I just need to put the contents of appsettings to the past.
Then it is simple to operate and:
It is important to note that Stackexchange.redis is not running on mono!
It will prompt you not to connect to redis!! This is really not tolerated.
But I can run, there's gotta be a solution. ~~stackexchange.redis.mono is a version that can run on Mono!!
And you just need to replace the assembly to run normally. Because this is the same as the Stackexchange.redis assembly name, no other modifications are required. It's still very convenient.
It is necessary to note that in the local debugging, the use of Redis is the Windows version, when the release is the Linux version.
0x04, summary
In this process, also encountered some problems and doubts.
The main problem is that the Windows standalone version of Tinyfox debugging is unsuccessful and can only be switched back to the generic version.
The main question is to use Redis to do this number of restrictions, is temporarily thinking, do not know whether it is reasonable.
The Web API has an open source library, which has an extension of the limit of the number of times, interested to see
Https://github.com/WebApiContrib/WebAPIContrib/tree/master/src/WebApiContrib
It uses concurrentdictionary to implement a lightweight cache.
One might ask what is the relationship between ASP. NET MVC, ASP. NET Web API, Nancyfx
The following is my personal opinion (not necessarily correct, I would say):
MVC clearly contains the three parts of M, V, C
The Web API can be said to contain only the two parts of M, C
In this case, the Web API is a subset of MVC,
So, the Web API can do, MVC can do, so there are many companies are directly using MVC to develop interfaces
Nancyfx with Web API words, and not much of a relationship
The Web API makes it easy to build HTTP services and is also based on restful
Nancyfx is a lightweight, HTTP-based framework that can also be used to build restful APIs.
If you want to say something, it's HTTP and restful.
Nancyfx and MVC, it's not too much of a relationship.
But they can count on two good friends, have a common interest, can do the same thing
API, the way to achieve a lot, how to choose to see the individual ideas.
More articles about NANCYFX, you can go to Nancy's big miscellaneous painting
Nancy's Implementation API