How to parse http headers in Go - http

I have http response headers shipped in logs from elsewhere. In my log file I have things like :-
Date: Fri, 21 Mar 2014 06:45:15 GMT\r\nContent-Encoding: gzip\r\nLast-Modified: Tue, 20 Aug 2013 15:45:41 GMT\r\nServer: nginx/0.8.54\r\nAge: 18884\r\nVary: Accept-Encoding\r\nContent-Type: text/html\r\nCache-Control: max-age=864000, public\r\nX-UA-Compatible: IE=Edge,chrome=1\r\nTiming-Allow-Origin: *\r\nContent-Length: 14888\r\nExpires: Mon, 31 Mar 2014 06:45:15 GMT\r\n
Given the above as string, how go I parse it into Header object as described in net/http . One way would be to split the string myself and map the key, values... But I am looking to avoid doing that by hand and use the standard (or well maintained 3rd party) library to parse it... Any pointers?

The builtin parser is in textproto. You can either use this directly, or add a
fake HTTP request header and use ReadRequest in the http package. Either way
you need to wrap your data into a bufio.Reader, here I'm just assuming we're
starting with a string.
With textproto:
logEntry := "Content-Encoding: gzip\r\nLast-Modified: Tue, 20 Aug 2013 15:45:41 GMT\r\nServer: nginx/0.8.54\r\nAge: 18884\r\nVary: Accept-Encoding\r\nContent-Type: text/html\r\nCache-Control: max-age=864000, public\r\nX-UA-Compatible: IE=Edge,chrome=1\r\nTiming-Allow-Origin: *\r\nContent-Length: 14888\r\nExpires: Mon, 31 Mar 2014 06:45:15 GMT\r\n"
// don't forget to make certain the headers end with a second "\r\n"
reader := bufio.NewReader(strings.NewReader(logEntry + "\r\n"))
tp := textproto.NewReader(reader)
mimeHeader, err := tp.ReadMIMEHeader()
if err != nil {
log.Fatal(err)
}
// http.Header and textproto.MIMEHeader are both just a map[string][]string
httpHeader := http.Header(mimeHeader)
log.Println(httpHeader)
and with http.ReadRequest:
logEntry := "Content-Encoding: gzip\r\nLast-Modified: Tue, 20 Aug 2013 15:45:41 GMT\r\nServer: nginx/0.8.54\r\nAge: 18884\r\nVary: Accept-Encoding\r\nContent-Type: text/html\r\nCache-Control: max-age=864000, public\r\nX-UA-Compatible: IE=Edge,chrome=1\r\nTiming-Allow-Origin: *\r\nContent-Length: 14888\r\nExpires: Mon, 31 Mar 2014 06:45:15 GMT\r\n"
// we need to make sure to add a fake HTTP header here to make a valid request.
reader := bufio.NewReader(strings.NewReader("GET / HTTP/1.1\r\n" + logEntry + "\r\n"))
logReq, err := http.ReadRequest(reader)
if err != nil {
log.Fatal(err)
}
log.Println(logReq.Header)
https://golang.org/pkg/net/textproto

Related

R RestRserve Add Etag to static path

Let's say I have a REST API using RestRserve like this, is there a way to add an Etag to enable caching on cloud services?
writeLines("Hello World", "myfile.txt")
app <- Application$new(content_type = "application/json")
app$add_static("/", ".")
backend <- BackendRserve$new()
# backend$start(app, http_port = 8080)
req <- Request$new(path = "/myfile.txt", method = "GET")
app$process_request(req)$headers
#> $Server
#> [1] "RestRserve/0.4.1001"
As we see, there is no Etag.
Example using Go fiber
Using GO fiber, I would use it like this:
package main
import (
"flag"
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/etag"
)
var (
port = flag.String("port", ":3000", "Port to listen on")
)
func main() {
app := fiber.New()
app.Use(etag.New())
app.Static("/", ".")
log.Fatal(app.Listen(*port))
}
and then querying localhost:3000/myfile.txt I would see headers like this
HTTP/1.1 200 OK
Date: Fri, 18 Mar 2022 13:13:44 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Last-Modified: Fri, 21 Jan 2022 16:24:47 GMT
Etag: "12-823400506"
Connection: close
Hello World
Is there a way to add Etag headers to static files using RestRserve?
As of RestRserve version 1.1.1 (on CRAN), there is an ETag Middleware class.
Use it like so:
# ... code like before
etag <- ETagMiddleware$new()
app$append_middleware(etag)
# ...
See also https://restrserve.org/reference/ETagMiddleware.html

Web API's - how to select variables of interest in httr::get() request in R

I'm learning to use APIs with R's httr package and the US Data Census API as my API.
When I make the request by manually entering the URL in the GET(), the request works.
try_using_manual<- httr::GET("https://api.census.gov/data/2014/pep/natstprc?get=STNAME,POP&DATE=7&for=state:01&key=XXXX")
> try_using_manual
Response [https://api.census.gov/data/2014/pep/natstprc?get=STNAME,POP&DATE=7&for=state:01&key=7e3d28d2d9facb45c12a0b63f9d31b709d917dda]
Date: 2018-12-08 03:08
Status: 200
Content-Type: application/json;charset=utf-8
Size: 65 B
[["STNAME","POP","DATE","state"],
When I try to do the same request, but instead building arguments from the GET() function, my request fails.
> try_using_arg <- httr::GET(url = "https://api.census.gov/data/2014/pep/natstprc",
+ query = list("STNAME"=":*","POP"=":*","DATE"=7))
> try_using_arg
Response [https://api.census.gov/data/2014/pep/natstprc?STNAME=%3A%2A&POP=%3A%2A&DATE=7]
Date: 2018-12-08 03:20
Status: 400
Content-Type: text/plain;charset=ISO-8859-1
Size: 74 B
Any ideas on how to call those variables, STNAME,POP,DATE,state as seen in try_using_manual in try_using_arg?
Any and all help is appreciated!

Transfer Encoding chunked with okhttp only delivers full result

I am trying to get some insights on a chunked endpoint and therefore planned to print what the server sends me chunk by chunk. I failed to do so so I wrote a test to see if OkHttp/Retrofit are working as I expect it.
The following test should deliver some chunks to the console but all I get is the full response.
I am a bit lost what I am missing to even make the MockWebServer of OkHttp3 sending me chunks.
I found this retrofit issue entry but the answer is a bit ambiguous for me: Chunked Transfer Encoding Response
class ChunkTest {
#Rule
#JvmField
val rule = RxImmediateSchedulerRule() // custom rule to run Rx synchronously
#Test
fun `test Chunked Response`() {
val mockWebServer = MockWebServer()
mockWebServer.enqueue(getMockChunkedResponse())
val retrofit = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(OkHttpClient.Builder().build())
.build()
val chunkedApi = retrofit.create(ChunkedApi::class.java)
chunkedApi.getChunked()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
System.out.println(it.string())
}, {
System.out.println(it.message)
})
mockWebServer.shutdown()
}
private fun getMockChunkedResponse(): MockResponse {
val mockResponse = MockResponse()
mockResponse.setHeader("Transfer-Encoding", "chunked")
mockResponse.setChunkedBody("THIS IS A CHUNKED RESPONSE!", 5)
return mockResponse
}
}
interface ChunkedApi {
#Streaming
#GET("/")
fun getChunked(): Flowable<ResponseBody>
}
Test console output:
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$2 execute
INFO: MockWebServer[49293] starting to accept connections
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$3 processOneRequest
INFO: MockWebServer[49293] received request: GET / HTTP/1.1 and responded: HTTP/1.1 200 OK
THIS IS A CHUNKED RESPONSE!
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$2 acceptConnections
INFO: MockWebServer[49293] done accepting connections: Socket closed
I expected to be more like (body "cut" every 5 bytes):
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$2 execute
INFO: MockWebServer[49293] starting to accept connections
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$3 processOneRequest
INFO: MockWebServer[49293] received request: GET / HTTP/1.1 and responded: HTTP/1.1 200 OK
THIS
IS A
CHUNKE
D RESPO
NSE!
Nov 06, 2018 4:08:15 PM okhttp3.mockwebserver.MockWebServer$2 acceptConnections
INFO: MockWebServer[49293] done accepting connections: Socket closed
The OkHttp Mockserver does chunk the data, however it looks like the LoggingInterceptor waits until the whole chunks buffer is full then it displays it.
From this nice summary about HTTP streaming:
The use of Transfer-Encoding: chunked is what allows streaming within a single request or response. This means that the data is transmitted in a chunked manner, and does not impact the representation of the content.
With that in mind, we are dealing with 1 "request / response", which means we'll have to do our chunks retrieval before getting the entire response. Then pushing each chunk in our own buffer, all that on an OkHttp network interceptor.
Here is an example of said NetworkInterceptor:
class ChunksInterceptor: Interceptor {
val Utf8Charset = Charset.forName ("UTF-8")
override fun intercept (chain: Interceptor.Chain): Response {
val originalResponse = chain.proceed (chain.request ())
val responseBody = originalResponse.body ()
val source = responseBody!!.source ()
val buffer = Buffer () // We create our own Buffer
// Returns true if there are no more bytes in this source
while (!source.exhausted ()) {
val readBytes = source.read (buffer, Long.MAX_VALUE) // We read the whole buffer
val data = buffer.readString (Utf8Charset)
println ("Read: $readBytes bytes")
println ("Content: \n $data \n")
}
return originalResponse
}
}
Then of course we register this Network Interceptor on the OkHttp client.

Why is the server sending an http 200 response instead of expected 206?

History:
We are delivering .mp4 video via http partial request streaming. It works fine except on iPad/iPhone and we have followed all of the recommended procedures for encoding .mp4 files for iOS devices so the issue is not the files themselves. The video files are stored outside of the root of the web server so we have written code to open and stream the video upon request. Our testing has brought us to the point of comparing the headers of our "streamed" video (working but not in IOS) and linking directly to the file (working in all instances but not a possible solution as we need to store files outside of web root). Here is a comparison of the working vs. non-working headers.
link direct to file (working for IOS):
request 1
Status Code 200 OK
Accept-Ranges:bytes
Content-Length:62086289
Content-Type:video/mp4
Date:Sun, 26 Jul 2015 08:11:15 GMT
ETag:"60be3570ae7ed01:0"
Last-Modified:Fri, 24 Apr 2015 16:48:06 GMT
Server:Microsoft-IIS/7.0
X-Powered-By:ASP.NET
request 2
Status Code:206 Partial Content
Accept-Ranges:bytes
Content-Length:62086289
Content-Range:bytes 0-62086288/62086289
Content-Type:video/mp4
Date:Sun, 26 Jul 2015 08:11:16 GMT
ETag:"60be3570ae7ed01:0"
Last-Modified:Fri, 24 Apr 2015 16:48:06 GMT
Server:Microsoft-IIS/7.0
X-Powered-By:ASP.NET
opening file for streaming (not working for IOS)
request 1
Status Code 200 OK
Accept-Ranges:bytes
Cache-Control:private
Content-Length:62086289
Content-Range:bytes 0-62086289/62086289
Content-Type:video/mp4
Date:Sun, 26 Jul 2015 16:55:22 GMT
Server:Microsoft-IIS/7.0
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
request 2
Status Code 200 OK
Accept-Ranges:bytes
Cache-Control:private
Content-Length:62086289
Content-Range:bytes 0-62086289/62086289
Content-Type:video/mp4
Date:Sun, 26 Jul 2015 16:55:22 GMT
Server:Microsoft-IIS/7.0
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
Notice that the non working version's second request stays at Status Code 200 and does not change to 206. This is what we are focusing on as possibly being the issue but are at a loss. Below is the code to open the file and send to server. Note the way we are setting up the header.
Dim iStream As System.IO.Stream
' Buffer to read 10K bytes in chunk:
Dim buffer(10000) As Byte
' Length of the file:
Dim length As Integer=0
' Total bytes to read:
Dim dataToRead As Long=0
Dim isPartialFile As Boolean = False
Dim totalDelivered As Long = 0
Dim totalFileSize As Long = 0
Dim filename As String
filename = fileID & file.Extension
iStream = New System.IO.FileStream(filePath, System.IO.FileMode.Open,IO.FileAccess.Read, IO.FileShare.Read)
' Total bytes to read:
dataToRead = iStream.Length
totalFileSize = iStream.Length
Response.ContentType = "video/mp4"
Response.AddHeader("Accept-Ranges", "bytes")
Response.AddHeader("Content-Range", "bytes 0-" + totalFileSize.ToString + "/" + totalFileSize.ToString)
Response.AddHeader("Content-Length", totalFileSize.ToString)
' Read the bytes.
While dataToRead > 0
' Verify that the client is connected.
If Response.IsClientConnected Then
' Read the data in buffer
length = iStream.Read(buffer, 0, 10000)
' Write the data to the current output stream.
Response.OutputStream.Write(buffer, 0, length)
' Flush the data to the HTML output.
Response.Flush()
ReDim buffer(10000) ' Clear the buffer
dataToRead = dataToRead - length
Else
isPartialFile = True
'record the data read in the database HERE (partial file)
'totalDelivered = totalFileSize - dataToRead
'prevent infinite loop if user disconnects
dataToRead = -1
End If
End While
iStream.Close()
Response.End()
How do we modify the above code to make it work with IOS devices assuming that the issue is with the 206 response header?

Error connecting to azure blob storage API from R

I am attempting to work with Azure storage via the REST API in R. I'm using the package httr which overlays Curl.
Setup
You can use R-fiddle: http://www.r-fiddle.org/#/fiddle?id=vh8uqGmM
library(httr)
requestdate<-format(Sys.time(),"%a, %d %b %Y %H:%M:%S GMT")
url<-"https://preconstuff.blob.core.windows.net/pings?restype=container&comp=list"
sak<-"Q8HvUVJLBJK+wkrIEG6LlsfFo19iDjneTwJxX/KXSnUCtTjgyyhYnH/5azeqa1bluGD94EcPcSRyBy2W2A/fHQ=="
signaturestring<-paste0("GET",paste(rep("\n",12),collapse=""),
"x-ms-date:",requestdate,"
x-ms-version:2009-09-19
/preconstuff/pings
comp:list
restype:container")
headerstuff<-add_headers(Authorization=paste0("SharedKey preconstuff:",
RCurl::base64(digest::hmac(key=sak,
object=enc2utf8(signaturestring),
algo= "sha256"))),
`x-ms-date`=requestdate,
`x-ms-version`= "2009-09-19")
Trying to list blobs:
content(GET(url,config = headerstuff, verbose() ))
Error
Top level message
The MAC signature found in the HTTP request 'Q8HvUVJLBJK+wkrIEG6LlsfFo19iDjneTwJxX/KXSnUCtTjgyyhYnH/5azeqa1bluGD94EcPcSRyBy2W2A/fHQ==' is not the same as any computed signature.
Response content
[1] "<?xml version=\"1.0\" encoding=\"utf-8\"?><Error>
<Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\nRequestId:1ab26da5-0001-00dc-6ddb-15e35c000000\nTime:2015-03-26T17:51:42.7190620Z</Message>
<AuthenticationErrorDetail>The MAC signature found in the HTTP request 'NTM1ODZjMjhhZmMyZGM3NDM0YTFjZDgwNGE0ODVmMzVjNDhkNjBkNzk1ZjNkZjJjOTNlNjUxYTMwMjRhNzNlYw==' is not the same as any computed signature. Server used following string to sign:
'GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Thu, 26 Mar 2015 17:52:37 GMT\nx-ms-version:2009-09-19\n/preconstuff/pings\ncomp:list\nrestype:container'.
</AuthenticationErrorDetail></Error>"
Verbose output
-> GET /pings?restype=container&comp=list HTTP/1.1
-> User-Agent: curl/7.39.0 Rcurl/1.95.4.5 httr/0.6.1
-> Host: preconstuff.blob.core.windows.net
-> Accept-Encoding: gzip
-> Accept: application/json, text/xml, application/xml, */*
-> Authorization: SharedKey preconstuff:OTRhNTgzYmY3OTY3M2UzNjk3ODdjMzk3OWM3ZmU0OTA4MWU5NTE2OGYyZGU3YzRjNjQ1M2NkNzY0ZTcyZDRhYQ==
-> x-ms-date: Thu, 26 Mar 2015 17:56:27 GMT
-> x-ms-version: 2009-09-19
->
<- HTTP/1.1 403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
<- Content-Length: 719
<- Content-Type: application/xml
<- Server: Microsoft-HTTPAPI/2.0
<- x-ms-request-id: 3d47770c-0001-0085-2313-6d466f000000
<- Date: Thu, 26 Mar 2015 17:56:27 GMT
<-
Resolving the error
Googling for this issue doesn't seem to yield a consistent cause, but it's likely due to bad formatting / request structure on my part. To that end I checked:
I've verified my key is correct (it's just c&p from portal)
I've made sure the date is correctly formatted
There was a recent documentDB SO that suggested it could be to be a clock skew issue and I do note that my x-ms-date is a second ahead of the Date in the response. I've tried sending a fixed value that was definitely in the past, but within the 15 mins tolerance. Didn't get a change in message.
Added encoding="Base64" in headerstuff further to an MSDN forum question but the same error message was returned
Further to #Serdar's answer, I incorporated the construction of a signature string (I've verified that this matches the one provided in the error message so it), then encoding in base64 a hmac-sha256 (using the secondary access key (sak) as the encryption key) version of the UTF8 converted signaturestring as the value to be used in the SharedKey authorisation.
Further to #Serdar's comment, the date used in the signature string and for the main request must be the same, so defined once and reused
Is there anything obviously wrong? Are there other things to check? Does the code work for others?
Looks like your problem is with the key. The string of the key you have provided is actually base64 encoded. You need to decode that to the raw vector before you use it to sign the request. For example:
url<-"https://preconstuff.blob.core.windows.net/pings?restype=container&comp=list"
sak<-"Q8HvUVJLBJK+wkrIEG6LlsfFo19iDjneTwJxX/KXSnUCtTjgyyhYnH/5azeqa1bluGD94EcPcSRyBy2W2A/fHQ=="
requestdate<-format(Sys.time(),"%a, %d %b %Y %H:%M:%S %Z", tz="GMT")
signaturestring<-paste0("GET",paste(rep("\n",12),collapse=""),
"x-ms-date:",requestdate,"
x-ms-version:2009-09-19
/preconstuff/pings
comp:list
restype:container")
headerstuff<-add_headers(Authorization=paste0("SharedKey preconstuff:",
RCurl::base64(digest::hmac(key=RCurl::base64Decode(sak, mode="raw"),
object=enc2utf8(signaturestring),
algo= "sha256", raw=TRUE))),
`x-ms-date`=requestdate,
`x-ms-version`= "2009-09-19")
content(GET(url,config = headerstuff, verbose() ))
There are no more authentication errors this way, though no blobs are listed. Perhaps that's a different issue.
Also, I changed the way the date/time was created to more "safely" change the local time to GMT.
It looks like you are using the key of your account directly in the Authorization header. To authenticate a request, you must sign the request with the key for the account that is making the request and pass that signature as part of the request. Please see Authentication for the Azure Storage Services for more information on how to construct the Authorization header.
Please also note that the service returns StringToSign in the error response. So, what your code should have done is to apply the following formula to StringToSign="GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Wed, 25 Mar 2015 22:24:12 GMT\nx-ms-version:2014-02-14\n/preconstuff/pings\ncomp:list\nrestype:container" (without quotes):
Signature=Base64(HMAC-SHA256(AccountKey, UTF8(StringToSign)))
How the service calculates StringToSign is explained in detail in the link shared above.
Worked trough the example code by MrFlick above and to get it to work I had to change a few things.
The date string has to be set in an English locale, for example:
lct <- Sys.getlocale("LC_TIME")
Sys.setlocale("LC_TIME", "us")
requestdate <- format(Sys.time(),"%a, %d %b %Y %H:%M:%S %Z", tz="GMT")
Sys.setlocale("LC_TIME", lct)
The 'signaturestring' should be formated with \n between parameters:
signaturestring <- paste0("GET", paste(rep("\n", 12), collapse=""),
"x-ms-date:", requestdate,
"\nx-ms-version:2009-09-19\n/preconstuff/pings\ncomp:list\nrestype:container")
EDIT: Following procedure works for me. Based on Steph Locke example.
library(httr)
library(RCurl)
azureBlobCall <- function(url, verb, key, requestBody=NULL, headers=NULL, ifMatch="", md5="") {
urlcomponents <- httr::parse_url(url)
account <- gsub(".blob.core.windows.net", "", urlcomponents$hostname, fixed = TRUE)
container <- urlcomponents$path
# get timestamp in us locale
lct <- Sys.getlocale("LC_TIME"); Sys.setlocale("LC_TIME", "us")
`x-ms-date` <- format(Sys.time(),"%a, %d %b %Y %H:%M:%S %Z", tz="GMT")
Sys.setlocale("LC_TIME", lct)
# if requestBody exist get content length in bytes and content type
`Content-Length` <- ""; `Content-Type` <- ""
if(!is.null(requestBody)) {
if(class(requestBody) == "form_file") {
`Content-Length` <- (file.info(requestBody$path))$size
`Content-Type` <- requestBody$type
} else {
requestBody <- enc2utf8(as.character(requestBody))
`Content-Length` <- nchar(requestBody, "bytes")
`Content-Type` <- "text/plain; charset=UTF-8"
}
}
# combine timestamp and version headers with any input headers, order and create the CanonicalizedHeaders
headers <- setNames(c(`x-ms-date`, "2015-04-05", unlist(headers)),
c("x-ms-date", "x-ms-version", unclass(names(unlist(headers)))))
headers <- headers[order(names(headers))]
CanonicalizedHeaders <- paste(names(headers), headers, sep=":", collapse = "\n")
# create CanonicalizedResource headers and add any queries to it
if(!is.null(urlcomponents$query)) {
components <- setNames(unlist(urlcomponents$query), unclass(names(unlist(urlcomponents$query))))
componentstring <- paste0("\n", paste(names(components[order(names(components))]),
components[order(names(components))], sep=":", collapse = "\n"))
} else componentstring <- ""
CanonicalizedResource <- paste0("/",account,"/",container, componentstring)
# create the authorizationtoken
signaturestring <- paste0(verb, "\n\n\n", `Content-Length`, "\n", md5, "\n", `Content-Type`, "\n\n\n",
ifMatch, "\n\n\n\n", CanonicalizedHeaders, "\n", CanonicalizedResource)
requestspecificencodedkey <- RCurl::base64(
digest::hmac(key=RCurl::base64Decode(key, mode="raw"),
object=enc2utf8(signaturestring),
algo= "sha256", raw=TRUE)
)
authorizationtoken <- paste0("SharedKey ", account, ":", requestspecificencodedkey)
# make the call
headers_final <- add_headers(Authorization=authorizationtoken, headers, `Content-Type` = `Content-Type`)
call <- httr::VERB(verb=verb, url=url, config=headers_final, body=requestBody, verbose())
print("signaturestring");print(signaturestring);
print(headers_final); print(call)
return(content(call))
}
## Tests. Replace 'key' and 'accountName' with yours
key <- "YowThr***********RDw=="
# Creates a container named 'test'
azureBlobCall("https://accountName.blob.core.windows.net/test?restype=container", "PUT", key)
# Creates a blob named 'blob' under container 'test' with the content of "Hej världen!"
azureBlobCall("https://accountName.blob.core.windows.net/test/blob", "PUT", key,
headers = c("x-ms-blob-type"="BlockBlob"), requestBody = "Hej världen!") #upload_file("blob.txt"))
# List all blob in the container 'test'
azureBlobCall("https://accountName.blob.core.windows.net/test?comp=list&restype=container", "GET", key)
# deletes the blobl named 'blob'
azureBlobCall("https://accountName.blob.core.windows.net/test/blob", "DELETE", key)
# Creates a blob named 'blob' under container 'test' with and upload the file 'blob.txt'
azureBlobCall("https://accountName.blob.core.windows.net/test/blob", "PUT", key,
headers = c("x-ms-blob-type"="BlockBlob"), requestBody = upload_file("blob.txt"))
# deletes the container named 'test'
azureBlobCall("https://accountName.blob.core.windows.net/test?restype=container", "DELETE", key)

Resources