How to pass multiple strings into a url in http get? - http

This is my current code:
var dek string = "dk"
resp,err := c.Get("https://google."VALUEHERE"")
What I want to be able to is pass different strings into my url if I need a bunch of different ones.
So ideally would be something like:
resp,err := c.Get("https://google.dk/value1=%v&value2=%v", value1, value2)
Is this possible in any way?

Use fmt.Sprintf(...) to build a string that does not require encoding:
hostname := fmt.Sprintf("google.%s", "dk")
// => "google.dk"
Use the net/url package to build URLs so they are encoded properly:
u := &url.URL{Scheme: "https", Host: hostname}
fmt.Println(u)
// => https://google.dk
q := u.Query()
q.Add("value1", "foo")
q.Add("value2", "Hello, World!")
u.RawQuery = q.Encode()
fmt.Println(u)
// => https://google.dk?value1=foo&value2=Hello%2C+World%21
resp, err := c.Get(u.String())
// ...

Related

Generate AWS Signature in MarkLogic XQuery - can't reproduce example hash

I need to send a GET request to AWS from MarkLogic and sign the URL. I was using the AWS documentation to understand how the signature gets created but what I get and what I expect to get aren't the same.
Sample data is from this page.
let $canonicalRequest :=
'GET
/test.txt
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host
host:examplebucket.s3.amazonaws.com
host
UNSIGNED-PAYLOAD'
(: the value of this is correct, this is the same as the example, 3bfa292879f6447bbcda7001decf97f4a54dc650c8942174ae0a9121cf58ad04 :)
let $canonicalRequestHash := xdmp:sha256($canonicalRequest)
let $stringToSign :=
'AWS4-HMAC-SHA256
20130524T000000Z
20130524/us-east-1/s3/aws4_request
' ||
$canonicalRequestHash
let $signingKey := xdmp:hmac-sha256(xdmp:hmac-sha256(xdmp:hmac-sha256(xdmp:hmac-sha256("AWS4wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY","20130524"),"us-east-1"),"s3"),"aws4_request")
return xdmp:hmac-sha256($signingKey,$stringToSign)
I get
ec43271c228d0d408e25dd8ec1e3b71ed7c1dbfe5c76bd7f272d3bff665e1f16
I would like to get
aeeed9bbccd4d02ee5c0109b86d86835f995330da4c265957d157751f604d404
The secretKey parameter should be a binary node for the region, service, and signing keys. This should work:
xquery version "1.0-ml";
let $canonicalRequest :=
'GET
/test.txt
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host
host:examplebucket.s3.amazonaws.com
host
UNSIGNED-PAYLOAD'
(: the value of this is correct, this is the same as the example, 3bfa292879f6447bbcda7001decf97f4a54dc650c8942174ae0a9121cf58ad04 :)
let $canonicalRequestHash := xdmp:sha256($canonicalRequest)
let $stringToSign :=
'AWS4-HMAC-SHA256
20130524T000000Z
20130524/us-east-1/s3/aws4_request
' ||
$canonicalRequestHash
let $dateKey := xdmp:hmac-sha256("AWS4wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "20130524")
let $regionKey := xdmp:hmac-sha256(binary { xs:hexBinary($dateKey) }, "us-east-1")
let $serviceKey := xdmp:hmac-sha256(binary { xs:hexBinary($regionKey) }, "s3")
let $signingKey := xdmp:hmac-sha256(binary { xs:hexBinary($serviceKey) }, "aws4_request")
return xdmp:hmac-sha256(binary { xs:hexBinary($signingKey) }, $stringToSign)

How to add URL query parameters to HTTP GET request?

I am trying to add a query parameter to a HTTP GET request but somehow methods pointed out on SO (e.g. here) don't work.
I have the following piece of code:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "/callback", nil)
req.URL.Query().Add("code", "0xdead 0xbeef")
req.URL.Query().Set("code", "0xdead 0xbeef")
// this doesn't help
//req.URL.RawQuery = req.URL.Query().Encode()
if err != nil {
log.Fatal(err)
}
fmt.Printf("URL %+v\n", req.URL)
fmt.Printf("RawQuery %+v\n", req.URL.RawQuery)
fmt.Printf("Query %+v\n", req.URL.Query())
}
which prints:
URL /callback
RawQuery
Query map[]
Any suggestions on how to achieve this?
Playground example: https://play.golang.org/p/SYN4yNbCmo
Check the docs for req.URL.Query():
Query parses RawQuery and returns the corresponding values.
Since it "parses RawQuery and returns" the values what you get is just a copy of the URL query values, not a "live reference", so modifying that copy does nothing to the original query. In order to modify the original query you must assign to the original RawQuery.
q := req.URL.Query() // Get a copy of the query values.
q.Add("code", "0xdead 0xbeef") // Add a new value to the set.
req.URL.RawQuery = q.Encode() // Encode and assign back to the original query.
// URL /callback?code=0xdead+0xbeef
// RawQuery code=0xdead+0xbeef
// Query map[code:[0xdead 0xbeef]]
Note that your original attempt to do so didn't work because it simply parses the query values, encodes them, and assigns them right back to the URL:
req.URL.RawQuery = req.URL.Query().Encode()
// This is basically a noop!
You can directly build the query params using url.Values
func main() {
req, err := http.NewRequest("GET", "/callback", nil)
req.URL.RawQuery = url.Values{
"code": {"0xdead 0xbeef"},
}.Encode()
...
}
Notice the extra braces because each key can have multiple values.

Get property from reader.io object in golang

I'm new at golang and got some little problem:
I got remoteApi that give me some response when I'm making http request like here:
res, err := http.DefaultClient.Do(req)
the body of the response contains some json such as :
{
a: 'hello'
b: 5
c:[1,2,3]
}
I need to assign the value of "a" to other variable .
What is the best way to access one of res.Body properties? Ive tried to convert to json / string and so but no success
thanks
Something like this should work:
var s struct {
A string
}
err := json.NewDecoder(response.Body).Decode(&s)
// check err
result := s.A
Also please note that your JSON response example is not valid JSON (single quotes instead of double quotes, field names are not quoted, field separators missing) and will not be parsed successfully as such.

Is there a better way to parse this Map?

Fairly new to Go, essentially in the actual code I'm writing I plan to read from a file which will contain environment variables, i.e. API_KEY=XYZ. Means I can keep them out of Version control. The below solution 'works' but I feel like there is probably a better way of doing it.
The end goal is to be able to access the elements from the file like so
m["API_KEY"] and that would print XYZ. This may even already exist and I'm re-inventing the wheel, I saw Go has environment variables but it didn't seem to be what I was after specifically.
So any help is appreciated.
Playground
Code:
package main
import (
"fmt"
"strings"
)
var m = make(map[string]string)
func main() {
text := `Var1=Value1
Var2=Value2
Var3=Value3`
arr := strings.Split(text, "\n")
for _, value := range arr {
tmp := strings.Split(value, "=")
m[strings.TrimSpace(tmp[0])] = strings.TrimSpace(tmp[1])
}
fmt.Println(m)
}
First, I would recommend to read this related question: How to handle configuration in Go
Next, I would really consider storing your configuration in another format. Because what you propose isn't a standard. It's close to Java's property file format (.properties), but even property files may contain Unicode sequences and thus your code is not a valid .properties format parser as it doesn't handle Unicode sequences at all.
Instead I would recommend to use JSON, so you can easily parse it with Go or with any other language, and there are many tools to edit JSON texts, and still it is human-friendly.
Going with the JSON format, decoding it into a map is just one function call: json.Unmarshal(). It could look like this:
text := `{"Var1":"Value1", "Var2":"Value2", "Var3":"Value3"}`
var m map[string]string
if err := json.Unmarshal([]byte(text), &m); err != nil {
fmt.Println("Invalid config file:", err)
return
}
fmt.Println(m)
Output (try it on the Go Playground):
map[Var1:Value1 Var2:Value2 Var3:Value3]
The json package will handle formatting and escaping for you, so you don't have to worry about any of those. It will also detect and report errors for you. Also JSON is more flexible, your config may contain numbers, texts, arrays, etc. All those come for "free" just because you chose the JSON format.
Another popular format for configuration is YAML, but the Go standard library does not include a YAML parser. See Go implementation github.com/go-yaml/yaml.
If you don't want to change your format, then I would just use the code you posted, because it does exactly what you want it to do: process input line-by-line, and parse a name = value pair from each line. And it does it in a clear and obvious way. Using a CSV or any other reader for this purpose is bad because they hide what's under the hood (they intentionally and rightfully hide format specific details and transformations). A CSV reader is a CSV reader first; even if you change the tabulator / comma symbol: it will interpret certain escape sequences and might give you different data than what you see in a plain text editor. This is an unintended behavior from your point of view, but hey, your input is not in CSV format and yet you asked a reader to interpret it as CSV!
One improvement I would add to your solution is the use of bufio.Scanner. It can be used to read an input line-by-line, and it handles different styles of newline sequences. It could look like this:
text := `Var1=Value1
Var2=Value2
Var3=Value3`
scanner := bufio.NewScanner(strings.NewReader(text))
m := map[string]string{}
for scanner.Scan() {
parts := strings.Split(scanner.Text(), "=")
if len(parts) == 2 {
m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
if err := scanner.Err(); err != nil {
fmt.Println("Error encountered:", err)
}
fmt.Println(m)
Output is the same. Try it on the Go Playground.
Using bufio.Scanner has another advantage: bufio.NewScanner() accepts an io.Reader, the general interface for "all things being a source of bytes". This means if your config is stored in a file, you don't even have to read all the config into the memory, you can just open the file e.g. with os.Open() which returns a value of *os.File which also implements io.Reader, so you may directly pass the *os.File value to bufio.NewScanner() (and so the bufio.Scanner will read from the file and not from an in-memory buffer like in the example above).
1- You may read all with just one function call r.ReadAll() using csv.NewReader from encoding/csv with:
r.Comma = '='
r.TrimLeadingSpace = true
And result is [][]string, and input order is preserved, Try it on The Go Playground:
package main
import (
"encoding/csv"
"fmt"
"strings"
)
func main() {
text := `Var1=Value1
Var2=Value2
Var3=Value3`
r := csv.NewReader(strings.NewReader(text))
r.Comma = '='
r.TrimLeadingSpace = true
all, err := r.ReadAll()
if err != nil {
panic(err)
}
fmt.Println(all)
}
output:
[[Var1 Value1] [Var2 Value2] [Var3 Value3]]
2- You may fine-tune csv.ReadAll() to convert the output to the map, but the order is not preserved, try it on The Go Playground:
package main
import (
"encoding/csv"
"fmt"
"io"
"strings"
)
func main() {
text := `Var1=Value1
Var2=Value2
Var3=Value3`
r := csv.NewReader(strings.NewReader(text))
r.Comma = '='
r.TrimLeadingSpace = true
all, err := ReadAll(r)
if err != nil {
panic(err)
}
fmt.Println(all)
}
func ReadAll(r *csv.Reader) (map[string]string, error) {
m := make(map[string]string)
for {
tmp, err := r.Read()
if err == io.EOF {
return m, nil
}
if err != nil {
return nil, err
}
m[tmp[0]] = tmp[1]
}
}
output:
map[Var2:Value2 Var3:Value3 Var1:Value1]

go lang construct map based on filter criteria

I have below structures
type ModuleList []Module
type Module struct {
Id string
Items []Item
Env map[string]string
}
type Item struct {
Id string
Host string
}
I have a service which returns ModuleList; but I would like to create a function which can group ModuleList based on Module Env key value and return map[string]ModuleList or map[string]*Module
Can i have any sample function which does this ?
I had tried doing this
appsByGroup := make(map[string]ModuleList)
for _, app := range apps {
if _, ok := app.Env["APP_GROUP"]; ok {
appGroup := app.Env["APP_GROUP"]
appsByGroup[appGroup] = app
}
}
; but not quite sure how to add element to an array
If you want to group all the Modules by a APP_GROUP then you are pretty much correct. You are just not appending to the slice correctly:
appsByGroup := make(map[string]ModuleList)
for _, app := range apps {
if _, ok := app.Env["APP_GROUP"]; ok {
appGroup := app.Env["APP_GROUP"]
// app is of type Module while in appsGroup map, each string
// maps to a ModuleList, which is a slice of Modules.
// Hence your original line
// appsByGroup[appGroup] = app would not compile
appsByGroup[appGroup] = append(appsByGroup[appGroup], app)
}
}
Now you can access all the Modules (stored in a slice) in a group by using:
// returns slice of modules (as ModuleList) with
// Env["APP_GROUP"] == group
appGroups[group]

Resources