Related
I am extremely new to the programming and to the elixir. So I am very exited to learn as much as I can. But I've got a problem. I looking the way how to use my functions in another module. I am building the web-server which stores the key-value maps in the memory. To keep the maps temporary I've decided to use Agent. Here is the part of my code:
defmodule Storage do
use Agent
def start_link do
Agent.start_link(fn -> %{} end, name: :tmp_storage)
end
def set(key, value) do
Agent.update(:tmp_storage, fn map -> Map.put_new(map, key, value) end)
end
def get(key) do
Agent.get(:tmp_storage, fn map -> Map.get(map, key) end)
end
end
So I'm trying to put this functions to the routes of the web server:
defmodule Storage_router do
use Plug.Router
use Plug.Debugger
require Logger
plug(Plug.Logger, log: :debug)
plug(:match)
plug(:dispatch)
post "/storage/set" do
with {:ok, _} <- Storage.set(key, value) do
send_resp(conn, 200, "getting the value")
else
_ ->
send_resp(conn, 404, "nothing")
end
end
end
And I receive:
warning: variable "key" does not exist and is being expanded to "key()", please use parentheses to remove the ambiguity or change the variable name
lib/storage_route.ex:12
warning: variable "value" does not exist and is being expanded to "value()", please use parentheses to remove the ambiguity or change the variable name
lib/storage_route.ex:12
looking for any suggestions\help
I am extremly new to the programming and to the elixir.
I do not think it is wise to begin learning programming with elixir. I would start with python or ruby, and then after a year or two then I would try elixir.
The first thing you need to learn is how to post code. Search google for how to post code on stackoverflow. Then, you have to get your indenting all lined up. Are you using a computer programming text editor? If not, then you have to get one. There are many free ones. I use vim, which comes installed on Unix like computers. You can learn how to use vim by typing vimtutor in a terminal window.
Next, you have a syntax error in your code:
Agent.start_link(fn -> %{} end, name: :tmp_storage
end)
That should be:
Agent.start_link(fn -> %{} end, name: :tmp_storage)
The warning you got is because your code tries to do the equivalent of:
def show do
IO.puts x
end
Elixir and anyone else reading that code would ask, "What the heck is x?" The variable x is never assigned a value anywhere, and therefore the variable x does not exist, and you cannot output something that is non-existent. You do the same thing here:
with {:ok, _} <- Storage.set(key, value) do
send_resp(conn, 200, "getting the value")
else
_->
send_resp(conn, 404, "nothing")
end
You call the function:
Storage.set(key, value)
but the variables key and value were never assigned a value, and elixir (and anyone else reading that code) wonders, "What the heck are key and value?"
This is the way functions work:
b.ex:
defmodule MyFuncs do
def show(x, y) do
IO.puts x
IO.puts y
end
end
defmodule MyWeb do
def go do
height = 10
width = 20
MyFuncs.show(height, width)
end
end
In iex:
~/elixir_programs$ iex b.ex
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> MyWeb.go
10
20
:ok
iex(2)>
So, in your code you need to write something like this:
post "/storage/set" do
key = "hello"
value = 10
with {:ok, _} <- Storage.set(key, value) do
send_resp(conn, 200, "Server saved the key and value.")
else
_->
send_resp(conn, 404, "nothing")
end
end
However, that will store the same key/value for every post request. Presumably, you want to store whatever is sent in the body of the post request. Do you know the difference between a get request and a post request? A get request tacks data onto the end of the url, while a post request sends the data in the "body of the request", so there are different procedures for extracting the data depending on the type of the request.
What tutorial are you reading? This tutorial: https://www.jungledisk.com/blog/2018/03/19/tutorial-a-simple-http-server-in-elixir/, shows you how to extract the data from the body of a post request. The data in the body of a post request is just a string. If the string is in JSON format, then you can convert the string into an elixir map using Poison.decode!(), which will allow you to easily extract the values associated with the keys that you are interested in. For example:
post "/storage/set" do
{:ok, body_string, conn} = read_body(conn)
body_map = Poison.decode!(body_string)
IO.inspect(body_map) #This outputs to terminal window where server is running
message = get_in(body_map, ["message"])
send_resp(
conn,
201,
"Server received: #{message}\n"
)
end
Then you can use the following curl command in another terminal window to send a post request to that route:
$ curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"message": "hello world" }'
(-v => verbose output, -H => request header, -d => data)
Now, based on what I said was wrong with your code above, you should be wondering about this line:
{:ok, body_string, conn} = read_body(conn)
That line calls:
read_body(conn)
but the variable conn is not assigned a value anywhere. However, Plug invisibly creates the conn variable and assigns a value to it.
Here is a complete example using Agent to store post request data (following the tutorial I linked above):
simple_server
config/
lib/
simple_server/
application.ex
router.ex
storage.ex
test/
An elixir convention is to have a directory in the lib/ directory with the same name as your project, in this case that would be simple_server, then you give the modules you define names that reflect the directory structure. So, in router.ex you would define a module named SimpleServer.Router and in storage.ex you would define a module named SimpleServer.Storage. However, the . in a module name means nothing special to elixir, so you will not get an error if you decide to name your module F.R.O.G.S in the file lib/rocks.ex--and your code will work just fine.
router.ex:
defmodule SimpleServer.Router do
use Plug.Router
use Plug.Debugger
require Logger
plug(Plug.Logger, log: :debug)
plug(:match)
plug(:dispatch)
get "/storage/:key" do
resp_msg = case SimpleServer.Storage.get(key) do
nil -> "The key #{key} doesn't exist!\n"
val -> "The key #{key} has value #{val}.\n"
end
send_resp(conn, 200, resp_msg)
end
post "/storage/set" do
{:ok, body_string, conn} = read_body(conn)
body_map = Poison.decode!(body_string)
IO.inspect(body_map) #This outputs to terminal window where server is running
Enum.each(
body_map,
fn {key, val} -> SimpleServer.Storage.set(key,val) end
)
send_resp(
conn,
201,
"Server stored all key-value pairs\n"
)
end
match _ do
send_resp(conn, 404, "not found")
end
end
The first thing to note in the code above is the route:
get "/storage/:key" do
That will match a path like:
/storage/x
and plug will create a variable named key and assign it the value "x", like this:
key = "x"
Also, note that when you call a function:
width = 10
height = 20
show(width, height)
elixir looks at the function definition:
def show(x, y) do
IO.puts x
IO.puts y
end
and matches the function call to the def like this:
show(width, height)
| |
V V
def show( x , y) do
...
end
and performs the assignments:
x = width
y = height
Then, inside the function you can use the x and y variables. In this line:
Enum.each(
body_map,
# | | | | |
# V V V V V
fn {key, val} -> SimpleServer.Storage.set(key,val) end
)
Elixir will call the anonymous function passing values for key and val, like this:
func("x", "10")
Therefore, in the body of the anonymous function you can use the variables key and val:
SimpleServer.Storage.set(key,val)
because the variables key and val will already have been assigned values.
storage.ex:
defmodule SimpleServer.Storage do
use Agent
def start_link(_args) do #<*** Note the change here
Agent.start_link(fn -> %{} end, name: :tmp_storage)
end
def set(key, value) do
Agent.update(
:tmp_storage,
fn(map) -> Map.put_new(map, key, value) end
)
end
def get(key) do
Agent.get(
:tmp_storage,
fn(map) -> Map.get(map, key) end
)
end
end
application.ex:
defmodule SimpleServer.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
#moduledoc false
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
Plug.Adapters.Cowboy.child_spec(scheme: :http, plug: SimpleServer.Router, options: [port: 8085]),
{SimpleServer.Storage, []}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: SimpleServer.Supervisor]
Supervisor.start_link(children, opts)
end
end
mix.exs:
defmodule SimpleServer.MixProject do
use Mix.Project
def project do
[
app: :simple_server,
version: "0.1.0",
elixir: "~> 1.6",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {SimpleServer.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:poison, "~> 4.0"},
{:plug_cowboy, "~> 2.0"}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
]
end
end
Note, if you use the dependencies and versions specified in the tutorial you will get some warnings, including the warning:
~/elixir_programs/simple_server$ iex -S mix
...
...
12:48:57.767 [warn] Setting Ranch options together
with socket options is deprecated. Please use the new
map syntax that allows specifying socket options
separately from other options.
...which is an issue with Plug. Here are the dependencies and versions that I used to get rid of all the warnings:
{:poison, "~> 4.0"},
{:plug_cowboy, "~> 2.0"}
Also, when you list an application as a dependency, you no longer have to enter it in the :extra_applications list. Elixir will automatically start all the applications listed as dependencies before starting your application. See :applications v. :extra_applications.
Once the server has started, you can use another terminal window to send a post request with curl (or you can use some other program):
~$ curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"x": "10", "y": "20" }
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> POST /storage/set HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 23
>
* upload completely sent off: 23 out of 23 bytes
< HTTP/1.1 201 Created
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:23 GMT
< content-length: 34
< cache-control: max-age=0, private, must-revalidate
<
Server stored all key-value pairs
* Connection #0 to host localhost left intact
The > lines are the request, and the < lines are the response. Also, check the output in the terminal window where the server is running.
~$ curl -v http://localhost:8085/storage/z
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/z HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:30 GMT
< content-length: 25
< cache-control: max-age=0, private, must-revalidate
<
The key z doesn't exist!
* Connection #0 to host localhost left intact
.
~$ curl -v http://localhost:8085/storage/x
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/x HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:37 GMT
< content-length: 24
< cache-control: max-age=0, private, must-revalidate
<
The key x has value 10.
* Connection #0 to host localhost left intact
I'm not entirely sure what you're trying to accomplish, but the error is telling you that the key and value that are passed to the router with statement are not defined. Elixir thinks you are trying to call a function with those arguments because they are not "bound" to a value. That is why you are seeing warning: variable "value" does not exist and is being expanded to "value()"
I suppose this is not really an answer but maybe more an explanation of the error you're seeing.
You need to pull the key/value params out of your %Plug.Conn{} object (conn). The key/value variables have not yet been defined within the scope of your route. The conn object is only available because it is injected by the post macro provided by Plug.
I am not quite aware of what type of requests you're submitting to the router, but I'll assume it's JSON as an example. You can manually parse the body in your connection by doing something like:
with {:ok, raw_body} <- Plug.Conn.read_body(conn),
{:ok, body} <- Poison.decode(raw_body) do
key = Map.get(body, "key")
value = map.get(body, "value")
# ... other logic
end
The Plug project, however, provides a nice convenience plug for you to parse request bodies in a generic way: Plug.Parsers.
To implement this in your router, you just have to add the plug to the top of your router (below Plug.Logger I think):
plug Plug.Parsers,
parsers: [:urlencoded, :json]
json_decoder: Poison,
pass: ["text/*", "application/json"]
The :urlencoded part will parse your query parameters and the :json part will parse the body of the request.
Then below in your route, you can get the key/value params from your conn object in the :params key like so:
%{params: params} = conn
key = Map.get(params, "key")
value = Map.get(params, "value")
Also, I should note that the best JSON decoder at the moment is Jason which is basically a drop-in replacement for Poison, but faster.
Anyway, reading hexdocs really helps with figuring this stuff out and the Plug project has great documentation. I think Elixir is a great language to start programming with (although it's essential to learn object-oriented paradigms as well). Happy coding!
I'm new to using compojure, but have been enjoying using it so far. I'm
currently encountering a problem in one of my API endpoints that is generating
a large CSV file from the database and then passing this as the response body.
The problem I seem to be encountering is that the whole CSV file is being kept
in memory which is then causing an out of memory error in the API. What is the
best way to handle and generate this, ideally as a gzipped file? Is it possible
to stream the response so that a few thousand rows are returned at a time? When
I return a JSON response body for the same data, there is no problem returning
this.
Here is the current code I'm using to return this:
(defn complete
"Returns metrics for each completed benchmark instance"
[db-client response-format]
(let [benchmarks (completed-benchmark-metrics {} db-client)]
(case response-format
:json (json-grouped-output field-mappings benchmarks)
:csv (csv-output benchmarks))))
(defn csv-output [data-seq]
(let [header (map name (keys (first data-seq)))
out (java.io.StringWriter.)
write #(csv/write-csv out (list %))]
(write header)
(dorun (map (comp write vals) data-seq))
(.toString out)))
The data-seq is the results returned from the database, which I think is a
lazy sequence. I'm using yesql to perform the database call.
Here is my compojure resource for this API endpoint:
(defresource results-complete [db]
:available-media-types ["application/json" "text/csv"]
:allowed-methods [:get]
:handle-ok (fn [request]
(let [response-format (keyword (get-in request [:request :params :format] :json))
disposition (str "attachment; filename=\"nucleotides_benchmark_metrics." (name response-format) "\"")
response {:headers {"Content-Type" (content-types response-format)
"Content-Disposition" disposition}
:body (results/complete db response-format)}]
(ring-response response))))
Thanks to all the suggestion that were provided in this thread, I was able to create a solution using piped-input-stream:
(defn csv-output [data-seq]
(let [headers (map name (keys (first data-seq)))
rows (map vals data-seq)
stream-csv (fn [out] (csv/write-csv out (cons headers rows))
(.flush out))]
(piped-input-stream #(stream-csv (io/make-writer % {})))))
This differs from my solution because it does not realise the sequence using dorun and does not create a large String object either. This instead writes to a PipedInputStream connection asynchronously as described by the documentation:
Create an input stream from a function that takes an output stream as its
argument. The function will be executed in a separate thread. The stream
will be automatically closed after the function finishes.
Your csv-output function completely realises the dataset and turns it into a string. To lazily stream the data, you'll need to return something other than a concrete data type like a String. This suggests ring supports returning a stream, that can be lazily realised by Jetty. The answer to this question might prove useful.
I was also struggling with the streaming of large csv file. My solution was to use httpkit-channel to stream every single line of the data-seq to the client and then close the channel. My solution looks like that:
[org.httpkit.server :refer :all]
(fn handler [req]
(with-channel req channel (let [header "your$header"
data-seq ["your$seq-data"]]
(doseq [line (cons header data-seq)]
(send! channel
{:status 200
:headers {"Content-Type" "text/csv"}
:body (str line "\n")}
false))
(close channel))))
I was flooded with a primitive json body for fcm:
Body = mochijson2:encode([ {<<"operation">>, <<"create">>},{<<"notification_key_name">>, <<"console group">>},{<<"registration_ids">>, [<<"02aa6XXXX3c9b6d">>,<<"APA91bGtaXXXXXXXXXXXXoi4UH8vIdZk1X67A_9izpSFSHV3BXxdIwG">>]}]).
And send POST-request to create group according to documentation:
httpc:request(post, {Url, [{"Authorization", KeyApi}, {"project_id", ProjectId}], "application/json", Body},[{timeout, 5000}], []).
But I got error BadJsonFormat:
{ok,{{"HTTP/1.1",400,"Bad Request"},
[{"cache-control","private, max-age=0"},
{"date","Fri, 10 Mar 2017 16:19:37 GMT"},
{"accept-ranges","none"},
{"server","GSE"},
{"vary","Accept-Encoding"},
{"content-length","25"},
{"content-type","application/json; charset=UTF-8"},
{"expires","Fri, 10 Mar 2017 16:19:37 GMT"},
{"x-content-type-options","nosniff"},
{"x-frame-options","SAMEORIGIN"},
{"x-xss-protection","1; mode=block"},
{"alt-svc","quic=\":443\"; ma=2592000; v=\"36,35,34\""}],
"{\"error\":\"BadJsonFormat\"}"}}
But mochijson2:decode(Body) works fine, and it looks like properly formed json, but I get the error BadJsonFormat anyway.
What was wrong? How can I fix this?
The function mochijson2:encode doesn't return a string or a binary, but an iolist:
1> Body = mochijson2:encode([ {<<"operation">>, <<"create">>},{<<"notification_key_name">>, <<"console group">>},{<<"registration_ids">>, [<<"02aa6XXXX3c9b6d">>,<<"APA91bGtaXXXXXXXXXXXXoi4UH8vIdZk1X67A_9izpSFSHV3BXxdIwG">>]}]).
[123,
[34,<<"operation">>,34],
58,
[34,<<"create">>,34],
44,
[34,<<"notification_key_name">>,34],
58,
[34,<<"console group">>,34],
44,
[34,<<"registration_ids">>,34],
58,
[91,
[34,<<"02aa6XXXX3c9b6d">>,34],
44,
[34,<<"APA91bGtaXXXXXXXXXXXXoi4UH8vIdZk1X67A_9izpSF"...>>,
34],
93],
125]
There is nothing wrong with that, by itself. Using iolists instead of strings or binaries means that you don't have to create an expensive flat data structure, that you would just write to a file or a socket, after which you'd throw it away. Function like file:write_file and gen_tcp:send handle iolists just as well as strings or binaries.
However, httpc:request doesn't!
Let's test that by starting a server on port 1111 with netcat in a shell:
$ nc -l 1111
And then make a request from the Erlang shell:
3> httpc:request(post, {"http://127.0.0.1:1111", [], "application/json", Body},[{timeout, 5000}], []).
The netcat server shows this output:
POST / HTTP/1.1
content-type: application/json
content-length: 13
te:
host: 127.0.0.1:1111
connection: keep-alive
{"operation":"create",....
Note that the content-length is 13 instead of 159! httpc:request is able to send the iolist, but it uses the function length instead of iolist_size to generate the content-length header, and as a result the server only considers the first 13 bytes of the JSON object, which is not valid JSON by itself.
The solution is to pass iolist_to_binary(Body) to httpc:request instead of just Body.
I am using a Google API library for Racket to try to update a Google Calendar. (The API is incomplete so I'm extending it as I go.)
I seem to have trouble with adding events to a calendar using the events.insert put call. The code that does the put looks like:
(define (insert-events calendar-id
event
#:token [t #f]
#:max-attendees [max-attendees #f]
#:send-notifications [send-notifications #f]
#:supports-attachments [supports-attachments #f])
(json-api-post (string->url
(format
"https://www.googleapis.com/calendar/v3/calendars/~a/events"
(form-urlencoded-encode calendar-id)))
event
#:token t))
(define (json-api-post u b
#:token [t #f]
#:headers [headers '()])
(define b* (jsexpr->bytes b))
(let retry ([t t]
[retry-counter 0])
(parse-json-response
(POST-string u b* (if t (cons (token->authorization-header t) headers) headers))
retry
t
retry-counter)))
(define (POST-string u b headers)
(port->string (post-pure-port u b headers)))
However, no matter how I use the call, I always get back an error 400 with the message: "Missing End Time". I checked out this question to ensure that I was sending my request correctly. Which I appear to be. For reference, the JSON object I am sending is:
{
"end": {
"dateTime": "2016-05-30T14:00:00-04:00"
},
"start": {
"dateTime": "2016-05-30T13:00:00-04:00"
}
}
Also, to make sure I was properly accessing the correct key and calendar id, I setup up an echo server for my local machine, and changed the url from google.com to localhost, my response seems normal:
POST /<Calendar-Id-Redacted> HTTP/1.1
Host: localhost
User-Agent: Racket/6.5.0.5 (net/http-client)
Content-Length: 116
Authorization: Bearer <Key-Redacted>
{
"end": {
"dateTime": "2016-05-30T14:00:00-04:00"
},
"start": {
"dateTime": "2016-05-30T13:00:00-04:00"
}
}
I seem to be doing everything correct. And even if there was a bug in my Racket code, sending in the exact same JSON object via Google's developer web console seems to work as intended. So why does sending this particular POST not work?
Close. But you are actually missing one piece of important data in the headers field of your POST request. Namely, the line:
Content-Type: application/json
Making your entire request to be:
POST /<Calendar-Id-Redacted> HTTP/1.1
Host: localhost
User-Agent: Racket/6.5.0.5 (net/http-client)
Content-Length: 116
Authorization: Bearer <Key-Redacted>
Content-Type: application/json
{
"end": {
"dateTime": "2016-05-30T14:00:00-04:00"
},
"start": {
"dateTime": "2016-05-30T13:00:00-04:00"
}
}
For some reason Google doesn't give you a nice error message if you are missing it, but if you do have it, Google will follow the API call.
This can be seen more clearly when you run the command in curl, if you run it like this (where you have a file called data.txt that has your json object in it:
curl -X POST -d #data.txt https://www.googleapis.com/calendar/v3/calendars/<Calendar-Id-Redacted>/events --header 'Authorization: Bearer <Key-Redacted>'
It will fail. But if you add the header with --header "Content-Type: application/json", the API will work as expected:
curl -X POST -d #data.txt https://www.googleapis.com/calendar/v3/calendars/<Calendar-Id-Redacted>/events --header 'Authorization: Bearer <Key-Redacted>' --header "Content-Type: application/json"
Adding that header to the library you have just requires one modification to the function that generates your post request:
(define (json-api-post u b
#:token [t #f]
#:headers [headers '("Content-Type: application/json")])
....)
It is exactly the same as your previous one, except we've modified the headers field to contain (by default), the list containing the string: "Content-Type: application/json". With this change, google should accept your API. (Note that there are many other ways to modify the headers function, this is just a simple way to do it.)
Putting it all together, your final code should look something like:
(define (insert-events calendar-id
event
#:token [t #f]
#:max-attendees [max-attendees #f]
#:send-notifications [send-notifications #f]
#:supports-attachments [supports-attachments #f])
(json-api-post (string->url
(format
"https://www.googleapis.com/calendar/v3/calendars/~a/events"
(form-urlencoded-encode calendar-id)))
event
#:token t))
(define (json-api-post u b
#:token [t #f]
#:headers [headers '("Content-Type: application/json")])
(define b* (jsexpr->bytes b))
(let retry ([t t]
[retry-counter 0])
(parse-json-response
(POST-string u b* (if t (cons (token->authorization-header t) headers) headers))
retry
t
retry-counter)))
(define (POST-string u b headers)
(port->string (post-pure-port u b headers)))
Hope that helps.
I am new in clojure , and I have one problem with this map.
{:status 200, :headers {Server openresty, Date Thu, 11 Feb 2016 11:35:11 GMT, Content-Type application/json; charset=utf-8, Transfer-Encoding chunked, Connection close, X-Source back, Access-Control-Allow-Origin *, Access-Control-Allow-Credentials true, Access-Control-Allow-Methods GET, POST}, :body {"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"base":"stations","main":{"temp":278.36,"pressure":1004,"humidity":65,"temp_min":276.05,"temp_max":280.15},"visibility":10000,"wind":{"speed":2.1,"deg":230},"rain":{"1h":0.2},"clouds":{"all":0},"dt":1455190219,"sys":{"type":1,"id":5091,"message":0.0549,"country":"GB","sunrise":1455175330,"sunset":1455210486},"id":2643743,"name":"London","cod":200}, :request-time 695, :trace-redirects [http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=44db6a862fba0b067b1930da0d769e98], :orig-content-encoding nil}
I want to read fields but with get-in can not make this job.Only with
(get-in location [:body])
I can read the body of map but when I make
(get-in location ["coord" "log"])
I just get a nil response.
How I can read this fields with clojure?
Thanks and sorry for my bad english.
The body is json, so you need to first use a json library to decode the body string before being able to treat it as a map:
Using cheshire, one of the major JSON libraries for Clojure, you could write this code like:
(require '[cheshire.core])
(-> location
:body
cheshire.core/parse-string
(get-in ["coord" "log"]))