I am pleased to introduce requestsR, an R interface for Python’s Requests module.

Background

R is a great language for dealing with web data. R has a bunch of fantastic packages for handling web-data the ones I recommend most being curl, httr, xml2, jsonlite and rvest.

This package is not meant to replace these packages, and with the exception of httr, this package is actually meant to be used in concert with them.

That said, performing complex web-requests is extremely difficult to do in R. Turns out, our brethren in the Python community developed a module that elegently deals with web requests meant to the most complex of web requests.

I built this package to unleash the power Requests inside of R.

I like to think of Requests as the Bo Jackson of web interaction tools.

Now that it has an R package it plays 2 languages but more importantly it’s API is clean, agile, elegant while also packing the force of a freight-train.

However, like Bo Jackson, there are times where using its powers may be overkill.

For really basic web-scraping tasks requestsR isn’t any better than httr or rvest, it will work just the same but if you are facing a daunting task and you cannot figure out a way to get it into the end-zone using httr or curl call on requestsR. If you can replicate the inputs to the request call this package will get the job done.


Quickstart

Now lets demonstrate how to use the package.


Installation

Right now the package is only available on Github.

You can install the package as follows:

devtools::install_github("abresler/requestsR")

Load packages

First thing we need to do is load the packages we are going to use.

library(dplyr)
library(reticulate)
library(jsonlite)
library(requestsR)
library(rvest)
library(listviewer)

Basic GET request

This example demonstrates how to perform the most basic GET request.

resp <-
  Get(url = 'https://api.github.com/events')

In addition to mimicking the Python API the package contains some additional functions that assist in parsing the response’s JSON and html.

Let’s demonstrate how to take the response and return parsed JSON.

json <- 
  resp %>%
  parse_response_json(is_data_frame = F)

We can now view the parsed JSON data.

json %>% 
  jsonedit()

Basic POST request

This example demonstrates how to use the [POST](https://en.wikipedia.org/wiki/POST_(HTTP) API.

In this example we will pass along additional parameters to the request, in this case data.

To pass along this parameter one must use either a named list or a reticulate::dict which mimics Python’s dictionary structure.

There are other cases where you may want to pass along reticulate::tuple parameters, something I will demonstrate in the next example.

post_resp <- 
  resp <-
  Post(url = 'http://httpbin.org/post', data = list(key = "value"))

As we did in the prior example we can parse the response and explore the JSON.

post_resp %>%
  parse_response_json(is_data_frame = FALSE) %>% 
  jsonedit()

Working with Tuples

There are times when you need to pass along tuples as inputs.

A tuple is the Python version of an unnamed list. Using the reticulate package we can easily create a tuple object in R, making it easy to generate requests that require tuple inputs

payload <-
  tuple(tuple('key1', 'value1'),
        tuple('key1', 'value2'))
resp_tuple <- Post('http://httpbin.org/post', data = payload)
resp_tuple %>% 
  parse_response_json()
$args
named list()

$data
[1] ""

$files
named list()

$form
$form$key1
[1] "value1" "value2"


$headers
$headers$Accept
[1] "*/*"

$headers$`Accept-Encoding`
[1] "gzip, deflate"

$headers$Connection
[1] "close"

$headers$`Content-Length`
[1] "23"

$headers$`Content-Type`
[1] "application/x-www-form-urlencoded"

$headers$Host
[1] "httpbin.org"

$headers$`User-Agent`
[1] "python-requests/2.18.4"


$json
NULL

$origin
[1] "67.254.199.56"

$url
[1] "http://httpbin.org/post"

Authenticating

Requests also makes it very easy to authenticate.

This package contains a special input parameter called auth.

If you need to authenticate just include a named list with the user information and it should work.

Let me demonstrate an example using Github’s API. Please note in order for you to recreate this you must substitute my github information with your own.

'https://api.github.com/user' %>% 
  Get(auth = list(user = "abresler",
            password = pwd)) %>% 
  parse_response_json(is_data_frame = TRUE) %>% 
  select(1:5)

Complex Requests

As stated in the introduction this is the primary reason why I built this package.

If you are trying to pull data from somewhere that requires headers, cookies, data and/or a payload that can be extremely difficult to do in R but really easy to do in Python via requests.

Let’s demonstrate using an example that includes headers, cookies and a payload parameter.

In this example I will also use a package function that converts a python dictionary object into an R named list. This is makes converting cURL parameters via a tool like this quick and easy for R use.

payload <- 
  "{'key1': 'value1', 'key2': 'value2'}" %>% 
  convert_dictionary_to_list()

headers <-
  "{'user-agent': 'my-app/0.0.1'}" %>%
  convert_dictionary_to_list()

Next lets use a named list to create the custom cookies.

cookies_list <-
  list(cookies_are = 'working')

Now we can put them all together and issue a POST request to showcase how simply you can go about making a complex request.

resp_complex <-
  Post(
    "http://httpbin.org/post",
    data = payload,
    headers = headers,
    cookies = cookies_list
  )

Now we can explore the results.

resp_complex %>% 
  parse_response_json() %>% 
  jsonedit()

Working with HTML and XML

One thing to always remember when using requestsR is that you can use it the same as you would use httr or any other R web interaction package.

All you need to do is to ensure that the response’s content is parsed to html and then it is ready to use with xml2, rvest or any package you use to work with HTML/XML content.

Let’s demonstrate how to do this with an example that pulls in the top web-story urls on the website Drudge Report

resp_drudge <- 
  "http://www.drudgereport.com/" %>% 
  Get()

page <- 
  resp_drudge %>% 
  parse_response_html()

page %>% 
  html_nodes(css = "#app_topstories a") %>% 
  html_attr('href')

Other Reponse Object Features

One of the benefits of the requests API is that it returns an object with tons of pertinent information. This includes not just the content of a successfully executed request but number of other potentially useful bis of information.

Lets use the object generated from the first request to take a look at a few of these useful bits of information.

Encoding

resp$apparent_encoding

Status Code

resp$status_code

Content

resp$content

Headers

resp$headers

There are a number of other things you can do with a response object. To better understand what they are you should spend some time working through the Python documentation.

That’s all for this introduction. I hope you find this package useful and that it also demonstrates the power of linking the Python and R.

LS0tCnRpdGxlOiAicmVxdWVzdHNSIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgY3NzOiB+L0Rlc2t0b3AvU2VtYW50aWMtVUkvZGlzdC9zZW1hbnRpYy5taW4uY3NzCiAgICB0aGVtZTogbHVtZW4KLS0tCgpJIGFtIHBsZWFzZWQgdG8gaW50cm9kdWNlIFtyZXF1ZXN0c1JdKGh0dHBzOi8vZ2l0aHViLmNvbS9hYnJlc2xlci9yZXF1ZXN0c1IpLCBhbiBSIGludGVyZmFjZSBmb3IgUHl0aG9uJ3MgW1JlcXVlc3RzXShodHRwOi8vZG9jcy5weXRob24tcmVxdWVzdHMub3JnL2VuL21hc3Rlci8pIG1vZHVsZS4KCiMjIyBCYWNrZ3JvdW5kCgpSIGlzIGEgZ3JlYXQgbGFuZ3VhZ2UgZm9yIGRlYWxpbmcgd2l0aCB3ZWIgZGF0YS4gIAoKUiBoYXMgYSBidW5jaCBvZiBmYW50YXN0aWMgcGFja2FnZXMgZm9yIGhhbmRsaW5nIHdlYi1kYXRhIHRoZSBvbmVzIEkgcmVjb21tZW5kIG1vc3QgYmVpbmcgW2N1cmxdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZXJvZW4vY3VybCksIFtodHRyXShodHRwczovL2dpdGh1Yi5jb20vaGFkbGV5L2h0dHIpLCBbeG1sMl0oaHR0cHM6Ly9naXRodWIuY29tL3ItbGliL3htbDIpLCBbanNvbmxpdGVdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZXJvZW4vanNvbmxpdGUpIGFuZCBbcnZlc3RdKGh0dHBzOi8vZ2l0aHViLmNvbS9oYWRsZXkvcnZlc3QpLiAgCgpUaGlzIHBhY2thZ2UgaXMgbm90IG1lYW50IHRvIHJlcGxhY2UgdGhlc2UgcGFja2FnZXMsIGFuZCB3aXRoIHRoZSBleGNlcHRpb24gb2YgaHR0ciwgdGhpcyBwYWNrYWdlIGlzIGFjdHVhbGx5IG1lYW50IHRvIGJlIHVzZWQgaW4gY29uY2VydCB3aXRoIHRoZW0gIAoKVGhhdCBzYWlkLCBwZXJmb3JtaW5nIGNvbXBsZXggd2ViLXJlcXVlc3RzIGlzIGV4dHJlbWVseSBkaWZmaWN1bHQgdG8gZG8gaW4gUi4KClR1cm5zIG91dCwgb3VyIGJyZXRocmVuIGluIHRoZSBQeXRob24gY29tbXVuaXR5IGhhdmUgc29sdmVkIHRoaXMgcHJvYmxlbSBhbmQgYnVpbHQgYSBtb2R1bGUgdGhhdCBlbGVnZW50bHkgZGVhbHMgd2l0aCB3ZWIgcmVxdWVzdHMgdGhhdCBjYW4gc2ltcGx5IGhhbmRsZSBldmVuIHRoZSBtb3N0IGNvbXBsZXggd2ViLXJlcXVlc3RzLgoKSSBidWlsdCB0aGlzIHBhY2thZ2UgdG8gdW5sZWFzaCB0aGUgcG93ZXIgYGBSZXF1ZXN0c2BgIGluc2lkZSBvZiBSLgoKSSBsaWtlIHRvIHRoaW5rIG9mIFJlcXVlc3RzIGFzIHRoZSBCbyBKYWNrc29uIG9mIHdlYiBpbnRlcmFjdGlvbiB0b29scy4gIE5vdyB0aGF0IGl0IGhhcyBhbiBSIHBhY2thZ2UgaXQgcGxheXMgMiBsYW5ndWFnZXMgYnV0IG1vcmUgaW1wb3J0YW50bHkgaXQncyBBUEkgIGlzIGNsZWFuLCBhZ2lsZSwgZWxlZ2FudCB3aGlsZSBhbHNvIHBhY2tpbmcgdGhlIGZvcmNlIG9mIGEgZnJlaWdodC10cmFpbi4KCiFbXShodHRwOi8vYXNiY2xsYy5jb20vcl9wYWNrYWdlcy9yZXF1ZXN0c1IvbG9nby9yZXFlc3RzUkxvZ28ucG5nKQoKSG93ZXZlciwgbGlrZSBCbyBKYWNrc29uLCB0aGVyZSBhcmUgdGltZXMgd2hlcmUgdXNpbmcgaXRzIHBvd2VycyBtYXkgYmUgb3ZlcmtpbGwuICAKCkZvciByZWFsbHkgYmFzaWMgd2ViLXNjcmFwaW5nIHRhc2tzIGByZXF1ZXN0c1JgIGlzbid0IGFueSBiZXR0ZXIgdGhhbiBgaHR0cmAgb3IgYHJ2ZXN0YCwgaXQgd2lsbCB3b3JrIGp1c3QgdGhlIHNhbWUgYnV0IGlmIHlvdSBhcmUgZmFjaW5nIGEgZGF1bnRpbmcgdGFzayBhbmQgeW91IGNhbm5vdCBmaWd1cmUgb3V0IGEgd2F5IHRvIGdldCBpdCBpbnRvIHRoZSBlbmQtem9uZSB1c2luZyBgaHR0cmAgb3IgYGN1cmxgIGNhbGwgb24gYHJlcXVlc3RzUmAuICBJZiB5b3UgY2FuIHJlcGxpY2F0ZSB0aGUgaW5wdXRzIHRvIHRoZSByZXF1ZXN0IGNhbGwgdGhpcyBwYWNrYWdlIHdpbGwgZ2V0IHRoZSBqb2IgZG9uZS4KCiMjIyBRdWlja3N0YXJ0CgpOb3cgbGV0cyBkZW1vbnN0cmF0ZSBob3cgdG8gdXNlIHRoZSBwYWNrYWdlLgoKIyMjIEluc3RhbGxhdGlvbgoKUmlnaHQgbm93IHRoZSBwYWNrYWdlIGlzIG9ubHkgYXZhaWxhYmxlIG9uIEdpdGh1Yi4gIAoKWW91IGNhbiBpbnN0YWxsIHRoZSBwYWNrYWdlIGFzIGZvbGxvd3M6CgpgYGB7ciBldmFsID0gRkFMU0V9CmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiYWJyZXNsZXIvcmVxdWVzdHNSIikKYGBgCgojIyMgTG9hZCBwYWNrYWdlcwoKRmlyc3QgdGhpbmcgd2UgbmVlZCB0byBkbyBpcyBsb2FkIHRoZSBwYWNrYWdlcyB3ZSBhcmUgZ29pbmcgdG8gdXNlLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocmV0aWN1bGF0ZSkKbGlicmFyeShqc29ubGl0ZSkKbGlicmFyeShyZXF1ZXN0c1IpCmxpYnJhcnkocnZlc3QpCmxpYnJhcnkobGlzdHZpZXdlcikKYGBgCgoKIyMjIEJhc2ljIEdFVCByZXF1ZXN0CgpUaGlzIGV4YW1wbGUgZGVtb25zdHJhdGVzIGhvdyB0byBwZXJmb3JtIHRoZSBtb3N0IGJhc2ljIApbR0VUXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9IeXBlcnRleHRfVHJhbnNmZXJfUHJvdG9jb2wjUmVxdWVzdF9tZXRob2RzKSByZXF1ZXN0LgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXJyb3I9RkFMU0V9CnJlc3AgPC0KICBHZXQodXJsID0gJ2h0dHBzOi8vYXBpLmdpdGh1Yi5jb20vZXZlbnRzJykKYGBgCgpJbiBhZGRpdGlvbiB0byBtaW1pY2tpbmcgdGhlIFB5dGhvbiBBUEkgdGhlIHBhY2thZ2UgY29udGFpbnMgc29tZSBhZGRpdGlvbmFsIGZ1bmN0aW9ucyAKdGhhdCBhc3Npc3QgaW4gcGFyc2luZyB0aGUgcmVzcG9uc2UncyBKU09OIGFuZCBodG1sLgoKTGV0J3MgZGVtb25zdHJhdGUgaG93IHRvIHRha2UgdGhlIHJlc3BvbnNlIGFuZCByZXR1cm4gcGFyc2VkIEpTT04uCgpgYGB7ciBlcnJvcj1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9Cmpzb24gPC0gCiAgcmVzcCAlPiUKICBwYXJzZV9yZXNwb25zZV9qc29uKGlzX2RhdGFfZnJhbWUgPSBGKQpgYGAKCldlIGNhbiBub3cgdmlldyB0aGUgcGFyc2VkIEpTT04gZGF0YS4KCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0UsIHByaW50LmtuaXRyX2thYmxlID0gVFJVRX0KanNvbiAlPiUgCiAganNvbmVkaXQoKQpgYGAKCiMjIyBCYXNpYyBQT1NUIHJlcXVlc3QKClRoaXMgZXhhbXBsZSBkZW1vbnN0cmF0ZXMgaG93IHRvIHVzZSB0aGUgW1BPU1RdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1BPU1RfKEhUVFApIEFQSS4gCgpJbiB0aGlzIGV4YW1wbGUgd2Ugd2lsbCBwYXNzIGFsb25nIGFkZGl0aW9uYWwgcGFyYW1ldGVycyB0byB0aGUgcmVxdWVzdCwgaW4gdGhpcyBjYXNlIGRhdGEuIAoKVG8gcGFzcyBhbG9uZyB0aGlzIHBhcmFtZXRlciBvbmUgbXVzdCB1c2UgZWl0aGVyIGEgbmFtZWQgbGlzdCBvciBhIGBgcmV0aWN1bGF0ZTo6ZGljdGBgIHdoaWNoIG1pbWljcyBQeXRob24ncyBkaWN0aW9uYXJ5IHN0cnVjdHVyZS4gIAoKVGhlcmUgYXJlIG90aGVyIGNhc2VzIHdoZXJlIHlvdSBtYXkgd2FudCB0byBwYXNzIGFsb25nIGBgYHJldGljdWxhdGU6OnR1cGxlYGBgIHBhcmFtZXRlcnMsIHNvbWV0aGluZyBJIHdpbGwgZGVtb25zdHJhdGUgaW4gdGhlIG5leHQgZXhhbXBsZS4KCmBgYHtyfQpwb3N0X3Jlc3AgPC0gCiAgcmVzcCA8LQogIHBvc3QodXJsID0gJ2h0dHA6Ly9odHRwYmluLm9yZy9wb3N0JywgZGF0YSA9IGxpc3Qoa2V5ID0gInZhbHVlIikpCmBgYAoKQXMgd2UgZGlkIGluIHRoZSBwcmlvciBleGFtcGxlIHdlIGNhbiBwYXJzZSB0aGUgcmVzcG9uc2UgYW5kIGV4cGxvcmUgdGhlIEpTT04uCgpgYGB7cn0KcG9zdF9yZXNwICU+JQogIHBhcnNlX3Jlc3BvbnNlX2pzb24oaXNfZGF0YV9mcmFtZSA9IEZBTFNFKSAlPiUgCiAganNvbmVkaXQoKQpgYGAKCiMjIyBXb3JraW5nIHdpdGggVHVwbGVzCgpUaGVyZSBhcmUgdGltZXMgd2hlbiB5b3UgbmVlZCB0byBwYXNzIGFsb25nIHR1cGxlcyBhcyBpbnB1dHMuICAKCkEgdHVwbGUgaXMgdGhlIFB5dGhvbiB2ZXJzaW9uIG9mIGFuIHVubmFtZWQgbGlzdC4gIFVzaW5nIHRoZSBgcmV0aWN1bGF0ZWAgcGFja2FnZSB3ZSBjYW4gZWFzaWx5IGNyZWF0ZSBhIHR1cGxlIG9iamVjdCBpbiBSLCBtYWtpbmcgaXQgZWFzeSB0byBnZW5lcmF0ZSByZXF1ZXN0cyB0aGF0IHJlcXVpcmUgdHVwbGUgaW5wdXRzCgpgYGB7cn0KcGF5bG9hZCA8LQogIHR1cGxlKHR1cGxlKCdrZXkxJywgJ3ZhbHVlMScpLAogICAgICAgIHR1cGxlKCdrZXkxJywgJ3ZhbHVlMicpKQpyZXNwX3R1cGxlIDwtIHBvc3QoJ2h0dHA6Ly9odHRwYmluLm9yZy9wb3N0JywgZGF0YSA9IHBheWxvYWQpCgpyZXNwX3R1cGxlICU+JSAKICBwYXJzZV9yZXNwb25zZV9qc29uKCkKCmBgYAoKIyMjIEF1dGhlbnRpY2F0aW5nCgpSZXF1ZXN0cyBhbHNvIG1ha2VzIGl0IHZlcnkgZWFzeSB0byBhdXRoZW50aWNhdGUuIAoKVGhpcyBwYWNrYWdlIGNvbnRhaW5zIGEgc3BlY2lhbCBpbnB1dCBwYXJhbWV0ZXIgY2FsbGVkIGBgYXV0aGBgLiAgCgpJZiB5b3UgbmVlZCB0byBhdXRoZW50aWNhdGUganVzdCBpbmNsdWRlIGEgbmFtZWQgbGlzdCB3aXRoIHRoZSB1c2VyIGluZm9ybWF0aW9uIGFuZCBpdCBzaG91bGQgd29yay4KCkxldCBtZSBkZW1vbnN0cmF0ZSBhbiBleGFtcGxlIHVzaW5nIEdpdGh1YidzIEFQSS4gIFBsZWFzZSBub3RlIGluIG9yZGVyIGZvciB5b3UgdG8gcmVjcmVhdGUgdGhpcyB5b3UgbXVzdCBzdWJzdGl0dXRlIG15IGdpdGh1YiBpbmZvcm1hdGlvbiB3aXRoIHlvdXIgb3duLgoKYGBge3J9CidodHRwczovL2FwaS5naXRodWIuY29tL3VzZXInICU+JSAKICBHZXQoYXV0aCA9IGxpc3QodXNlciA9ICJhYnJlc2xlciIsCiAgICAgICAgICAgIHBhc3N3b3JkID0gcHdkKSkgJT4lIAogIHBhcnNlX3Jlc3BvbnNlX2pzb24oaXNfZGF0YV9mcmFtZSA9IFRSVUUpICU+JSAKICBzZWxlY3QoMTo1KSAjIG9ubHkgaW5jbHVkZSBmaXJzdCA1IGNvbHVtbnMKYGBgCgojIyMgQ29tcGxleCBSZXF1ZXN0cwoKQXMgc3RhdGVkIGluIHRoZSBpbnRyb2R1Y3Rpb24gdGhpcyBpcyB0aGUgcHJpbWFyeSByZWFzb24gd2h5IEkgYnVpbHQgdGhpcyBwYWNrYWdlLgoKSWYgeW91IGFyZSB0cnlpbmcgdG8gcHVsbCBkYXRhIGZyb20gc29tZXdoZXJlIHRoYXQgcmVxdWlyZXMgaGVhZGVycywgY29va2llcywgZGF0YSBhbmQvb3IgYSBwYXlsb2FkIHRoYXQgY2FuIGJlICoqZXh0cmVtZWx5KiogZGlmZmljdWx0IHRvIGRvIGluIFIgYnV0IHJlYWxseSBlYXN5IHRvIGRvIGluIFB5dGhvbiB2aWEgcmVxdWVzdHMuIAoKTGV0J3MgZGVtb25zdHJhdGUgdXNpbmcgYW4gZXhhbXBsZSB0aGF0IGluY2x1ZGVzIGhlYWRlcnMsIGNvb2tpZXMgYW5kIGEgcGF5bG9hZCBwYXJhbWV0ZXIuCgpJbiB0aGlzIGV4YW1wbGUgSSB3aWxsIGFsc28gdXNlIGEgcGFja2FnZSBmdW5jdGlvbiB0aGF0IGNvbnZlcnRzIGEgcHl0aG9uIGRpY3Rpb25hcnkgb2JqZWN0IGludG8gYW4gUiBuYW1lZCBsaXN0LiAgVGhpcyBpcyBtYWtlcyBjb252ZXJ0aW5nIGNVUkwgcGFyYW1ldGVycyB2aWEgYSB0b29sIGxpa2UgW3RoaXNdKGh0dHBzOi8vY3VybC50cmlsbHdvcmtzLmNvbS8pIHF1aWNrIGFuZCBlYXN5IGZvciBSIHVzZS4KCgpgYGB7cn0KcGF5bG9hZCA8LSAKICAieydrZXkxJzogJ3ZhbHVlMScsICdrZXkyJzogJ3ZhbHVlMid9IiAlPiUgCiAgY29udmVydF9kaWN0aW9uYXJ5X3RvX2xpc3QoKQoKaGVhZGVycyA8LQogICJ7J3VzZXItYWdlbnQnOiAnbXktYXBwLzAuMC4xJ30iICU+JQogIGNvbnZlcnRfZGljdGlvbmFyeV90b19saXN0KCkKCmBgYAoKTmV4dCBsZXRzIHVzZSBhIG5hbWVkIGxpc3QgdG8gY3JlYXRlIHRoZSBjdXN0b20gY29va2llcy4KCmBgYHtyfQpjb29raWVzX2xpc3QgPC0KICBsaXN0KGNvb2tpZXNfYXJlID0gJ3dvcmtpbmcnKQpgYGAKCk5vdyB3ZSBjYW4gcHV0IHRoZW0gYWxsIHRvZ2V0aGVyIGFuZCBpc3N1ZSBhIFBPU1QgcmVxdWVzdCB0byBzaG93Y2FzZSBob3cgc2ltcGx5IHlvdSBjYW4gZ28gYWJvdXQgbWFraW5nIGEgY29tcGxleCByZXF1ZXN0LgoKYGBge3J9CnJlc3BfY29tcGxleCA8LQogIHBvc3QoCiAgICAiaHR0cDovL2h0dHBiaW4ub3JnL3Bvc3QiLAogICAgZGF0YSA9IHBheWxvYWQsCiAgICBoZWFkZXJzID0gaGVhZGVycywKICAgIGNvb2tpZXMgPSBjb29raWVzX2xpc3QKICApCmBgYAoKTm93IHdlIGNhbiBleHBsb3JlIHRoZSByZXN1bHRzLgoKYGBge3J9CnJlc3BfY29tcGxleCAlPiUgCiAgcGFyc2VfcmVzcG9uc2VfanNvbigpICU+JSAKICBqc29uZWRpdCgpCmBgYAoKIyMjIFdvcmtpbmcgd2l0aCBIVE1MIGFuZCBYTUwKCk9uZSB0aGluZyB0byBhbHdheXMgcmVtZW1iZXIgd2hlbiB1c2luZyBgcmVxdWVzdHNSYCBpcyB0aGF0IHlvdSBjYW4gdXNlIGl0IHRoZSBzYW1lIGFzIHlvdSB3b3VsZCB1c2UgYGh0dHJgIG9yIGFueSBvdGhlciBSIHdlYiBpbnRlcmFjdGlvbiBwYWNrYWcuCgpBbGwgeW91IG5lZWQgdG8gZG8gaXMgdG8gZW5zdXJlIHRoYXQgdGhlIHJlc3BvbnNlJ3MgY29udGVudCBpcyBwYXJzZWQgdG8gaHRtbCBhbmQgdGhlbiBpdCBpcyByZWFkeSB0byB1c2Ugd2l0aCB4bWwyLCBydmVzdCBvciBhbnkgcGFja2FnZSB0b29sIHlvdSB1c2UgdG8gd29yayB3aXRoIEhUTUwvWE1MIGNvbnRlbnQuCgpMZXQncyBkZW1vbnN0cmF0ZSBob3cgdG8gZG8gdGhpcyB3aXRoIGFuIGV4YW1wbGUgdGhhdCBwdWxscyBpbiB0aGUgdG9wIHdlYi1zdG9yeSB1cmxzIG9uIHRoZSB3ZWJzaXRlIFtEcnVkZ2UgUmVwb3J0XSgiaHR0cDovL3d3dy5kcnVkZ2VyZXBvcnQuY29tLyIpCgpgYGB7cn0KcmVzcF9kcnVkZ2UgPC0gCiAgImh0dHA6Ly93d3cuZHJ1ZGdlcmVwb3J0LmNvbS8iICU+JSAKICBHZXQoKQoKcGFnZSA8LSAKICByZXNwX2RydWRnZSAlPiUgCiAgcGFyc2VfcmVzcG9uc2VfaHRtbCgpCgpwYWdlICU+JSAKICBodG1sX25vZGVzKGNzcyA9ICIjYXBwX3RvcHN0b3JpZXMgYSIpICU+JSAKICBodG1sX2F0dHIoJ2hyZWYnKQpgYGAKCiMjIyBPdGhlciBSZXBvbnNlIE9iamVjdCBGZWF0dXJlcwoKT25lIG9mIHRoZSBiZW5lZml0cyBvZiB0aGUgcmVxdWVzdHMgQVBJIGlzIHRoYXQgaXQgcmV0dXJucyBhbiBvYmplY3Qgd2l0aCB0b25zIG9mIHBlcnRpbmVudCBpbmZvcm1hdGlvbi4gIFRoaXMgaW5jbHVkZXMgbm90IGp1c3QgdGhlIGNvbnRlbnQgb2YgYSBzdWNjZXNzZnVsbHkgZXhlY3V0ZWQgcmVxdWVzdCBidXQgbnVtYmVyIG9mIG90aGVyIHBvdGVudGlhbGx5IHVzZWZ1bCBiaXMgb2YgaW5mb3JtYXRpb24uCgpMZXRzIHVzZSB0aGUgb2JqZWN0IGdlbmVyYXRlZCBmcm9tIHRoZSBmaXJzdCByZXF1ZXN0IHRvIHRha2UgYSBsb29rIGF0IGEgZmV3IG9mIHRoZXNlIHVzZWZ1bCBiaXRzIG9mIGluZm9ybWF0aW9uLgoKKipFbmNvZGluZyoqCmBgYHtyfQpyZXNwJGFwcGFyZW50X2VuY29kaW5nCmBgYAoKKipTdGF0dXMgQ29kZSoqCmBgYHtyfQpyZXNwJHN0YXR1c19jb2RlCmBgYAoKKipDb250ZW50KioKYGBge3J9CnJlc3AkY29udGVudApgYGAKCioqSGVhZGVycyoqCmBgYHtyfQpyZXNwJGhlYWRlcnMKYGBgCgpUaGVyZSBhcmUgYSBudW1iZXIgb2Ygb3RoZXIgdGhpbmdzIHlvdSBjYW4gZG8gd2l0aCBhIHJlc3BvbnNlIG9iamVjdC4gIFRvIGJldHRlciB1bmRlcnN0YW5kIHdoYXQgdGhleSBhcmUgeW91IHNob3VsZCBzcGVuZCBzb21lIHRpbWUgd29ya2luZyB0aHJvdWdoIHRoZSBbUHl0aG9uIGRvY3VtZW50YXRpb25dKGh0dHA6Ly9kb2NzLnB5dGhvbi1yZXF1ZXN0cy5vcmcvZW4vbWFzdGVyLykuCgpUaGF0J3MgYWxsIGZvciB0aGlzIGludHJvZHVjdGlvbi4gIEkgaG9wZSB5b3UgZmluZCB0aGlzIHBhY2thZ2UgdXNlZnVsIGFuZCB0aGF0IGl0IGFsc28gZGVtb25zdHJhdGVzIHRoZSBwb3dlciBvZiBsaW5raW5nIHRoZSBQeXRob24gYW5kIFIu