Код на Node JS
import * as request from 'request'
import * as crypto from 'crypto'
import * as querystring from 'querystring'
import * as url from 'url'
import 'es6-shim'
const W_BASEURL = "https://api.com"
const W_DEFAULT_API_VERSION = "2"
const W_DEFAULT_API_FORMAT = "json"
export class WClient {
constructor(private config: any, private masqueradeTarget?: string) {
if(!config.secretKey) throw new Error('config.secretKey is missing');
if(!config.apiKey) throw new Error('config.apiKey is missing');
this.config.options = this.config.options || {};
}
public get(path: string, params?: any, options?: any): Promise<any> {
return this.request("GET", path, params, options)
}
public post(path: string, body?: any, options?: any): Promise<any> {
return this.request("POST", path, body, options)
}
public put(path: string, body?: any, options?: any): Promise<any> {
return this.request("PUT", path, body, options)
}
public delete(path: string, body?: any, options?: any): Promise<any> {
return this.request("DELETE", path, body, options)
}
/**
* returns a new W client which is ostensibly authenticated as the supplied
* target
*
* @param target an account ID or an SRN
*/
public masqueraded(target: string): WClient {
return new WClient(this.config, target);
}
private request(method: string, path: string, params: any = {}, options: any = {}): Promise<any> {
if(!path)
throw "path required"
let requestOptions = this.buildRequestOptions(method, path, params, options)
return new Promise((resolve, reject) => {
request(requestOptions, (err, res) => {
if(err)
throw err;
else if(res.statusCode >= 200 && res.statusCode < 300)
resolve(res.body || {})
else
reject(res.body || { statusCode: res.statusCode })
})
})
}
private buildRequestOptions(method: string, path: string, params: any, options: any): request.UrlOptions & request.CoreOptions {
options = options || {};
let parsedUrl = url.parse(url.resolve(this.config.baseUrl || W_BASEURL, path), true)
let json = !(options.headers || {}).hasOwnProperty('Content-Type') || options.headers['Content-Type'] == 'application/json';
let requestOptions: request.UrlOptions & request.CoreOptions = {
...this.config.options,
...options,
url: parsedUrl.protocol + "//" + parsedUrl.host + parsedUrl.pathname, // no querystring here!
method: method,
headers: {
...this.config.options.headers,
...options.headers,
"X-Api-Version": this.config.apiVersion || W_DEFAULT_API_VERSION,
"X-Api-Key": this.config.apiKey
},
qs: {
...this.config.qs,
...options.qs,
timestamp: new Date().getTime(),
format: this.config.format || W_DEFAULT_API_FORMAT
},
json: json
};
if(requestOptions.method == "GET")
requestOptions.qs = Object.assign(requestOptions.qs, params)
else
requestOptions.body = params
Object.assign(requestOptions.qs, parsedUrl.query);
if(this.masqueradeTarget && !('masqueradeAs' in requestOptions))
requestOptions.qs.masqueradeAs = this.masqueradeTarget;
requestOptions.headers["X-Api-Signature"] = this.buildSignature(requestOptions);
return requestOptions
}
private buildSignature(requestOptions: request.UrlOptions & request.CoreOptions): string {
let buffers: Buffer[] = [];
const encoding = 'utf8';
buffers.push(Buffer.from(requestOptions.url.toString(), encoding));
buffers.push(Buffer.from(requestOptions.url.toString().indexOf('?') < 0 ? "?" : "&", encoding));
buffers.push(Buffer.from(querystring.stringify(requestOptions.qs), encoding));
if(requestOptions.body) {
if(typeof requestOptions.body == 'string')
buffers.push(Buffer.from(requestOptions.body, encoding));
else if(requestOptions.body instanceof Buffer)
buffers.push(requestOptions.body);
else
buffers.push(Buffer.from(JSON.stringify(requestOptions.body), encoding));
}
return crypto.createHmac("sha256", this.config.secretKey)
.update(Buffer.concat(buffers))
.digest("hex")
}
}
Код на JS
"use strict";
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var request = require("request");
var crypto = require("crypto");
var querystring = require("querystring");
var url = require("url");
require("es6-shim");
var W_BASEURL = "https://api.com";
var W_DEFAULT_API_VERSION = "2";
var W_DEFAULT_API_FORMAT = "json";
var Client = (function () {
function Client(config, masqueradeTarget) {
this.config = config;
this.masqueradeTarget = masqueradeTarget;
if (!config.secretKey)
throw new Error('config.secretKey is missing');
if (!config.apiKey)
throw new Error('config.apiKey is missing');
this.config.options = this.config.options || {};
}
Client.prototype.get = function (path, params, options) {
return this.request("GET", path, params, options);
};
Client.prototype.post = function (path, body, options) {
return this.request("POST", path, body, options);
};
Client.prototype.put = function (path, body, options) {
return this.request("PUT", path, body, options);
};
Client.prototype.delete = function (path, body, options) {
return this.request("DELETE", path, body, options);
};
Client.prototype.masqueraded = function (target) {
return new Client(this.config, target);
};
Client.prototype.request = function (method, path, params, options) {
if (params === void 0) { params = {}; }
if (options === void 0) { options = {}; }
if (!path)
throw "path required";
var requestOptions = this.buildRequestOptions(method, path, params, options);
return new Promise(function (resolve, reject) {
request(requestOptions, function (err, res) {
if (err)
throw err;
else if (res.statusCode >= 200 && res.statusCode < 300)
resolve(res.body || {});
else
reject(res.body || { statusCode: res.statusCode });
});
});
};
Client.prototype.buildRequestOptions = function (method, path, params, options) {
options = options || {};
var parsedUrl = url.parse(url.resolve(this.config.baseUrl || W_BASEURL, path), true);
var json = !(options.headers || {}).hasOwnProperty('Content-Type') || options.headers['Content-Type'] == 'application/json';
var requestOptions = __assign({}, this.config.options, options, {
url: parsedUrl.protocol + "//" + parsedUrl.host + parsedUrl.pathname,
method: method,
headers: __assign({},
this.config.options.headers,
options.headers,
{ "X-Api-Version": this.config.apiVersion || W_DEFAULT_API_VERSION,
"X-Api-Key": this.config.apiKey }),
qs: __assign({}, this.config.qs, options.qs,
{ timestamp: new Date().getTime(),
format: this.config.format || W_DEFAULT_API_FORMAT }), json: json });
if (requestOptions.method == "GET")
requestOptions.qs = Object.assign(requestOptions.qs, params);
else
requestOptions.body = params;
Object.assign(requestOptions.qs, parsedUrl.query);
if (this.masqueradeTarget && !('masqueradeAs' in requestOptions))
requestOptions.qs.masqueradeAs = this.masqueradeTarget;
requestOptions.headers["X-Api-Signature"] = this.buildSignature(requestOptions);
return requestOptions;
};
Client.prototype.buildSignature = function (requestOptions) {
var buffers = [];
var encoding = 'utf8';
buffers.push(Buffer.from(requestOptions.url.toString(), encoding));
buffers.push(Buffer.from(requestOptions.url.toString().indexOf('?') < 0 ? "?" : "&", encoding));
buffers.push(Buffer.from(querystring.stringify(requestOptions.qs), encoding));
if (requestOptions.body) {
if (typeof requestOptions.body == 'string')
buffers.push(Buffer.from(requestOptions.body, encoding));
else if (requestOptions.body instanceof Buffer){
console.log("BUFFER");
console.log(requestOptions.body);
buffers.push(requestOptions.body);
}
else
buffers.push(Buffer.from(JSON.stringify(requestOptions.body), encoding));
}
return crypto.createHmac("sha256", this.config.secretKey)
.update(Buffer.concat(buffers))
.digest("hex");
};
return Client;
}());
exports.Client = Client;
Сделал так, но есть недоработки, такие как:
Что есть:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Linq;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using ConfigAccess;
using LogAccess;
using System.Collections.Specialized;
namespace WApiLib
{
public class WApi
{
private static readonly string domain = Configuration.Get()["W:Domain"];
private static readonly string apiKey = Configuration.Get()["W:ApiKey"];
private static readonly string secKey = Configuration.Get()["W:SecKey"];
public HttpWebResponse Get(string path)
{
return Get(path, new Dictionary<string, object>());
}
public HttpWebResponse Get(string path, Dictionary<string, object> queryParams)
{
return Request("GET", path, queryParams);
}
public HttpWebResponse Post(string path, Dictionary<string, object> body)
{
return Request("POST", path, body);
}
private HttpWebResponse Request(string method, string path, Dictionary<string, object> body)
{
Dictionary<string, object> queryParams = new Dictionary<string, object>();
if (method.Equals("GET"))
queryParams = body;
queryParams.Add("timestamp", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
string queryString = queryParams.Aggregate("", (previous, current) => previous + "&" + current.Key + "=" + current.Value).Remove(0, 1);
string url = domain + path + "?" + queryString;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.ContentType = "application/json";
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
if (!method.Equals("GET"))
{
url += JsonConvert.SerializeObject(body);
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
writer.Write(JsonConvert.SerializeObject(body));
}
request.Headers["X-Api-Key"] = apiKey;
request.Headers["X-Api-Signature"] = CalcAuthSigHash(secKey, url);
request.Headers["X-Api-Version"] = Configuration.Get()["W:ApiVersion"];
try
{
return (HttpWebResponse)request.GetResponse();
}
catch (WebException e)
{
string msg = new StreamReader(e.Response.GetResponseStream()).ReadToEnd();
Log.Error(msg);
throw new SystemException(msg);
}
}
private static byte[] GetBytes(string str)
{
return Encoding.UTF8.GetBytes(str);
}
private static string GetString(byte[] bytes)
{
return BitConverter.ToString(bytes);
}
private static String CalcAuthSigHash(string key, string value)
{
HMACSHA256 hmac = new HMACSHA256(GetBytes(key));
string hash = GetString(hmac.ComputeHash(GetBytes(value))).Replace("-", "");
return hash;
}
}
}
Код с typescript достаточно хорошо переводится на C#, благодаря наличию типов. Однако могут возникнуть небольшие сложности со специальными возможностями, типа Object.assign
, который позволяет добавлять в один объект поля из другого. Однако это может быть решено использованием нескольких Dictionary
.
TypeScript позволяет объявлять поля и сопоставлять их с одноименными параметрами непосредственно в определении конструктора:
constructor(private config: any, private masqueradeTarget?: string) {
if(!config.secretKey) throw new Error('config.secretKey is missing');
if(!config.apiKey) throw new Error('config.apiKey is missing');
this.config.options = this.config.options || {};
}
Здесь создаются два приватных поля, которые инициализируются значениями параметров и устанавливается значение по умолчанию для config.options
, если его не передали.
в C# это может выглядеть так:
private JObject config; // был использован JObject, из-за неизвестного формата. лучше использовать конкретный класс или интерфейс
private string masqueradeTarget;
public WClient(JObject config, string masqueradeTarget = null)
{
if (!config["SecretKey"].HasValues) throw new ArgumentException("config.secretKey is missing");
if (!config["ApiKey"].HasValues) throw new ArgumentException("config.apiKey is missing");
this.config = config;
this.config["Options"] = this.config["Options"].HasValues ? this.config["Options"] : new JObject();
this.masqueradeTarget = masqueradeTarget;
/* остальная инициализация полей, например создание httpClient */
client = new HttpClient();
}
Метод masqueraded
возвращает новый объект этого класса, которому передается существующий config
и устанавливается параметр masqueradeTarget
public WClient Masqueraded(string target)
{
return new WClient(config, target);
}
Методы вызывающие Request
вырождаются как и в TypeScript в одну строку:
public Task<JObject> Get(string path, Dictionary<string, string> @params = null, JObject options = null) => Request(HttpMethod.Get, path, @params, options ?? new JObject());
public Task<JObject> Post(string path, object body = null, JObject options = null) => Request(HttpMethod.Post, path, body, options ?? new JObject());
public Task<JObject> Put(string path, object body = null, JObject options = null) => Request(HttpMethod.Put, path, body, options ?? new JObject());
public Task<JObject> Delete(string path, object body = null, JObject options = null) => Request(HttpMethod.Delete, path, body, options ?? new JObject());
Сам метод Request
, за счет использования HttpClient
становится довольно простым:
private HttpClient client;
private async Task<JObject> Request(HttpMethod method, string path, object @params = null, JObject options = null)
{
if (path == null)
throw new ArgumentNullException("path required");
HttpRequestMessage requestOptions = BuildRequestOptions(method, path, @params, options); // собираем запрос
var response = await client.SendAsync(requestOptions); // шлем запрос
var content = await response.Content.ReadAsStringAsync(); // получаем результат
return JObject.Parse(content); // разбираем результат и возвращаем
}
Далее начинается магия с построением запроса.
Get
ему добавляются в QueryString переданные параметрыbody
masqueradeAs
, если соответствующее поле присутствуетЗа запрос в данном случае будет отвечать объект класса HttpRequestMessage
Данный класс имеет три основных свойства:
Структура метода может выглядеть так:
private HttpRequestMessage BuildRequestOptions(HttpMethod method, string path, object @params, JObject options = null)
{
var requestUri = /* собираем requesturi */;
if(method == HttpMethod.Get)
{
/* Добавляем параметры в request uri */
}
if (!string.IsNullOrEmpty(masqueradeTarget))
{
/* Добавляем параметр masqueradeAs в requestUri со значением masqueradeTarget */
}
/* проверка что пришло в body и на основе проверки создание необходимого объекта Content, например: */
HttpContent content;
if(body.GetType() == typeof(string)){
content = new StringContent(body.ToString());
}else if(body.GetType() == typeof(byte[])){
content = new ByteArrayContent((byte[])body);
}else{ // и т.д. если в body несколько полей разных типов можно воспользоваться MultipartFormDataContent
// иначе сериализуем в JSON
content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
}
var rm = new HttpRequestMessage()
{
Method = method,
RequestUri = requestUri,
Content = content
};
/* заполнение Headers */
rm.Headers.Add("X-Api-Version", /* value */ );
/* далее получаем подпись на основе requestUri и body и записываем так же в headers */
rm.Headers["X-Api-Signature"] = BuildSignature(requestUri, body);
return rm;
}
В текущей реализации подпись выполняется не совсем корректно: не учитывается тип body.
Нужно сделать по аналогии с созданием Content
- проверить тип, и на основе этого получить byte[]
.
После этого объединить этот массив с тем что вернул GetBytes
для url и только потом передать в ComputeHash
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Нужна база с такими двумерными списками, как ее правильно записать? Вряд ли получится сделать public DbSet<List<List<string>>> lst { get; set; } (я не пробовал)
Подскажите пожалуйста, разрабатываю приложение WPF под FrameWork 40, т
Необходимо сделать фабрику (проще лямбду конечно), которая бы выдавала свободный dbcontext из пула