И снова я сделал это!


В одном очень хорошем блоге есть хорошая статья о начале работы с REST API к Windows Azure Storage Services.

Но информации по некоторым вопросам там явно недостаточно. Автор, к сожалению, не дает с собой связаться. Так же странно себя ведет документация к REST API. Я потратил почти 6 часов на то, чтобы понять, что фильтры не входят в параметры канонизации строки. И исходников .NET не видно. Создавал на C# классы, пробовал-пробовал, отлаживал-отлаживал. Вроде, прорвался через фильтрацию.

И вот что мне очень не нравится в F# — его сильная ориентированность на .NET. Я, конечно, понимаю, что он так и задумывался. Но о какой автоматической параллельности может идти речь, если приходится вызывать в коде потоконебезопасные методы со сторонними эффектами?

Под катом продолжение моей библиотеки на F# для работы с Azure

// Learn more about F# at http://fsharp.net 

open System 
open System.IO 
open System.Net 
open System.Security.Cryptography
open System.Xml 
open System.Xml.XPath
open System.Web 
open Microsoft.FSharp.Collections

let create_functons user key =  
    let table_host = "ipv4.fiddler:10002"

    let getCanonicalizedResource (resource : string)=
        let delimiter_index = resource.IndexOf("?")
        if delimiter_index = -1 then 
            String.Format("/{0}{1}", user, resource)
        else 
            let splitters : char[] = "&".ToCharArray()
            let path = resource.Substring(0, delimiter_index)
            let data = Array.toList (resource.Substring(delimiter_index + 1).Split(splitters))
            let data_without_filters = List.filter (fun (x : string)-> not ((x.Chars 0) = '$')) data
            let get_key_val (x : string) = 
                let delimiter_index = x.IndexOf("=")
                (x.Substring(0, delimiter_index), x.Substring(delimiter_index + 1))
            let rec sort a = 
                if List.isEmpty a then
                    a 
                else 
                    let first (x, _) = x
                    let element = a.Head
                    let element_key = first(get_key_val element)
                    let less, more_or_equal = List.partition (fun x -> first(get_key_val x) < element_key) a.Tail
                    let more = List.filter (fun x -> not (x = element_key)) more_or_equal
                    (sort less)@[element]@(sort more)
                     
            let sorted_params = sort data_without_filters 
            if List.isEmpty data_without_filters then
                String.Format("/{0}{1}", user, path)
            else 
                let decode_param p =  
                    let key, value = get_key_val p 
                    HttpUtility.UrlDecode(key.ToLower()) + ":" + HttpUtility.UrlDecode(value)
                String.Format("/{0}{1}\n{2}", user, path, String.Join("\n", List.map decode_param sorted_params))            

    let stringToSign verb contentMD5 contentType date canonicalizedResource =
        System.String.Format("{0}\n{1}\n{2}\n{3}\n{4}", verb, contentMD5, contentType, date, canonicalizedResource)

    let getAuthorization (canonicalizedString : string) =
        let hasher = new HMACSHA256(System.Convert.FromBase64String(key))
        let data = System.Text.Encoding.UTF8.GetBytes(canonicalizedString)
        let signature = System.Convert.ToBase64String(hasher.ComputeHash(data))
        System.String.Format(System.Globalization.CultureInfo.InvariantCulture, "SharedKey {0}:{1}", user, signature)

    let getRequestObject request_method (uri : string) = 
        let request_path = String.Format("http://{0}{1}", table_host, uri)
        let current_date = System.DateTime.UtcNow.ToString("R", System.Globalization.CultureInfo.InvariantCulture)
        let contentMD5 = System.String.Empty
        let content_type = "application/atom+xml"
        let canonicalized_resource = getCanonicalizedResource uri
        let ms_version = "2009-09-19" 
        let canonicalized_request = stringToSign request_method contentMD5 content_type current_date canonicalized_resource
        let auth_header = getAuthorization (canonicalized_request)
        let request : HttpWebRequest = downcast WebRequest.Create(request_path)
        request.Method <- request_method 
        request.ProtocolVersion <- (System.Version(1, 1))
        request.ContentType <- content_type 
        request.Method <- request_method 
        request.Headers.Add("x-ms-date", current_date) 
        request.Headers.Add("Authorization", auth_header)
        request.Headers.Add("DataServiceVersion", "1.0;NetFx")
        request.Headers.Add("MaxDataServiceVersion", "1.0;NetFx")
        request.Headers.Add("Accept-Charset", "UTF-8")
        request.Headers.Add("x-ms-version", ms_version) 
        request 

    let listOfTables () =
        let uri = "/devstoreaccount1/Tables"
        let request_method = "GET"
        let request = getRequestObject request_method uri 
        let response : HttpWebResponse = downcast request.GetResponse()
        let reader = new StreamReader(response.GetResponseStream())
        let data = reader.ReadToEnd()
        let doc = XmlDocument()
        doc.LoadXml(data)
        let nsMgr = new XmlNamespaceManager(doc.NameTable)
        nsMgr.AddNamespace("atom", "http://www.w3.org/2005/Atom")
        nsMgr.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata")
        nsMgr.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices")
        let nodes = doc.SelectNodes("/atom:feed/atom:entry/atom:content/m:properties/d:TableName", nsMgr)
        seq {for i in nodes do yield i.InnerText}

    let createTable (table_name : string) =
        let uri = "/devstoreaccount1/Tables"
        let request_method = "POST"
        let request = getRequestObject request_method uri 
        request.Headers.Add("Accept-Charset", "UTF-8")
        let request_data = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?><entry xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\"><title /><updated>2009-03-18T11:48:34.9840639-07:00</updated><author><name/></author><id/><content type=\"application/xml\"><m:properties><d:TableName>{0}</d:TableName></m:properties></content></entry>"
        let ready_data = System.Text.Encoding.UTF8.GetBytes(System.String.Format(request_data, box table_name))
        request.ContentLength <- int64 ready_data.Length 
        let request_stream = request.GetRequestStream()
        request_stream.Write(ready_data, 0, ready_data.Length)
        let response : HttpWebResponse = downcast request.GetResponse()
        let dataReader = response.GetResponseStream()
        let streamReader = new StreamReader(dataReader)
        let str_response = streamReader.ReadToEnd()
        () 

    let deleteTable (table_name : string) = 
        let uri = String.Format("/devstoreaccount1/Tables('{0}')", table_name)
        let request_method = "DELETE"
        let request = getRequestObject request_method uri 
        let response : HttpWebResponse = downcast request.GetResponse()
        let reader = new StreamReader(response.GetResponseStream())
        let data = reader.ReadToEnd()
        () 

    let filter (table_name : string) (expression : string) = 
        let uri = String.Format("/devstoreaccount1/{0}()?{1}", table_name, expression) 
        let request_method = "GET"
        let request = getRequestObject request_method uri 
        let response : HttpWebResponse = downcast request.GetResponse()
        let reader = new StreamReader(response.GetResponseStream())
        let data = reader.ReadToEnd()
        data 

    let get (table_name : string) (partition_key : string) (row_key : string) (keys : List<string>) = 
        let uri = String.Format("/devstoreaccount1/{0}(PartitionKey='{1}',RowKey='{2}')", table_name, partition_key, row_key)
        let request_method = "GET"
        let request = getRequestObject request_method uri 
        let response : HttpWebResponse = downcast request.GetResponse()
        let reader = new StreamReader(response.GetResponseStream())
        let data = reader.ReadToEnd()
        let doc = XmlDocument()
        doc.LoadXml(data)
        let nsMgr = new XmlNamespaceManager(doc.NameTable)
        nsMgr.AddNamespace("atom", "http://www.w3.org/2005/Atom")
        nsMgr.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata")
        nsMgr.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices")
        let get_key key =  
            doc.SelectSingleNode("/atom:entry/atom:content/m:properties/d:" + key, nsMgr).InnerText
        List.map (fun x -> get_key x) keys 

    // requires list (property_name * type_name * value) 
    let insert (table_name : string) (key_value_list : List<string * string  * string>) = 
        let uri = "/devstoreaccount1/" + table_name
        let request_method = "POST"
        let request_template = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?><entry xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\"><title /><updated>{0}</updated><author><name /></author><id /><content type=\"application/xml\"><m:properties><d:Timestamp m:type=\"Edm.DateTime\">{0}</d:Timestamp>{1}</m:properties></content></entry>"
        let current_date = System.DateTime.UtcNow.ToString("s", System.Globalization.CultureInfo.InvariantCulture) + "Z"
        let nodes = String.Concat(List.map (fun (pn, tn, va) -> String.Format("<d:{0} {1}>{2}</d:{0}>", pn, tn, va)) key_value_list)
        let data = String.Format(request_template, current_date, nodes)
        let request = getRequestObject request_method uri 
        request.Headers.Add("Accept-Charset", "UTF-8")
        let ready_data = System.Text.Encoding.UTF8.GetBytes(data)
        request.ContentLength <- int64 ready_data.Length 
        let request_stream = request.GetRequestStream()
        request_stream.Write(ready_data, 0, ready_data.Length)
        let response : HttpWebResponse = downcast request.GetResponse()
        let dataReader = response.GetResponseStream()
        let streamReader = new StreamReader(dataReader)
        let str_response = streamReader.ReadToEnd()
        true 

    (listOfTables, createTable, deleteTable, insert, get, filter)
Реклама

Метки: , ,

Один ответ to “И снова я сделал это!”

  1. Василий Says:

    У меня вопрос: Я не пользуюсь Azure, у меня обычное Asp.net web api приложение на f#, странность в том, что когда я сериализую Record (да и любой другой объект в f#), в JSON то объект почему-то сериализуется с знаками @ на конце.
    type Human = {Name:string, Age:int}
    при вызове
    member this.Get(name: string, age:int ) =
    {Name = name, Age = age }

    возвращает такой JSON-объект :
    {Name@ : «Имечко», Age@ = «100»}
    Как бороться с этими собаками, мб знаете?

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s


%d такие блоггеры, как: