To run an actual Genie application, you recommend doing Genie.REPL.new_app("rest_service")
. However, this creates a lot of files that I don’t really need for a REST service. I tried reading through the docs to determine what an actual minimal working example would look like, but I’m a bit lost. Is there a more minimal example than new_app("blah")
?
I’ll try to figure this out on my own and will try to make a PR for docs or maybe a new_rest("blah")
command once I get some understanding.
1 Like
This is the best minimal example I could come up with:
using Genie
import Genie.Router: route, @params
import Base.convert
convert(::Type{Int}, s::SubString{String}) = parse(Int, s)
route("/sum/:x::Int/:y::Int") do
return @params(:x) + @params(:y)
end
route("/hello") do
return "Welcome to Genie!"
end
route("/") do
return "root"
end
Genie.AppServer.startup()
Base.JLOptions().isinteractive == 0 && wait()
3 Likes
@Seanny123 Correct
This is mine, in 7 lines of code:
using Genie
import Genie.Router: route
import JSON: json
route("/") do
(:message => "Hi there!") |> json
end
Genie.AppServer.startup()
I just put it all in a rest.jl
file and run it.
You can build from it by adding more routes, parsing params, etc.
1 Like
Can you run this outside of the REPL without the line at the end of my script Base.JLOptions().isinteractive == 0 && wait()
?
Small correction, this is the right approach (though a bit uglier) as it returns a proper JSON response (Content-Type: application/json
):
using Genie
import Genie.Router: route
import Genie.Renderer: respond
import JSON: json
route("/") do
Dict(:json => json(:message => "Hi there!")) |> respond
end
Genie.AppServer.startup()
I’ll add a bit of syntactic sugar to make it prettier, maybe:
json!(:message => "Hi there!")
It exits when running $ julia rest.jl
It’s because Genie.AppServer.startup()
calls @async HTTP.Servers.serve
. It should not start the server @async
to not exit.
I guess the simplest (though ugliest) is to add this at the end:
while true
sleep(1_000_000)
end
I’ll take a look - maybe I’ll remove the @async
and switch to starting the server with
@async Genie.AppServer.startup()
Or maybe add an async
param to startup()
?
Update 1
OK, I couldn’t stand that ugliness so I pushed a few enhancements
Please make sure you update:
pkg> up
Then you can have rest.jl
as:
using Genie
import Genie.Router: route
import Genie.Renderer: json!
Genie.config.session_auto_start = false
route("/") do
(:message => "Hi there!") |> json!
end
Genie.AppServer.startup(async = false)
And you can run it with
$ julia rest.jl
5 Likes
You could try this with Bukdu.jl v0.3.4
(tagged today)
# Bukdu v0.3.4
using Bukdu
struct RestController <: ApplicationController
conn::Conn
end
function init(c::RestController, region::String, site_id::Int, channel_id::Int)
render(JSON, (:init, region, site_id, channel_id))
end
function update(c::RestController, region::String, site_id::Int, channel_id::Int)
render(JSON, (:update, region, site_id, channel_id))
end
function build_params(c::RestController)
region::String = c.params.region
site_id::Int, channel_id::Int = parse.(Int, (c.params.site_id, c.params.channel_id))
(c, region, site_id, channel_id)
end
init(c::RestController) = init(build_params(c)...)
update(c::RestController) = update(build_params(c)...)
routes() do
get("/init/region/:region/site/:site_id/channel/:channel_id/", RestController, init)
get("/update/region/:region/site/:site_id/channel/:channel_id/", RestController, update)
end
Bukdu.start(8080)
#=
curl localhost:8080/init/region/west/site/1/channel/2/
curl localhost:8080/update/region/west/site/1/channel/2/
=#
1 Like
Similar to Genie
, I have to add Base.JLOptions().isinteractive == 0 && wait()
to the end of the script to stop it from terminating. Is there any other way to stop it from terminating?
If you updated to the latest master doing the wait
thing should not be necessary anymore. See above, you can pass Genie.AppServer.startup(async = false)
Sorry, I mis-typed. I did update Genie to master and it worked!
I’m trying Bukdu, because I’m currently having trouble using it in Docker, but I’ll open an issue on the repo when I have more details.
I understand how to pass an @params
via the URL, but is it also possible to send a formatted data via a “header”. For example, make a route able to respond to:
curl -H "Content-type: application/json" \
-X POST http://127.0.0.1:5000/messages -d '{"message":"Hello Data"}'
Which I’ve taken from this Flask example.
Can you please explain what you are trying to achieve? That’s a rather long page that you’re linking to…
Sorry for the long page. I was hoping you could just search for the command and read the code, despite a lack of familiarity with Python. This was not very considerate of me. Let me try again.
I would like to be able to send float values to my REST API. I thought I would have to accomplish this by sending a JSON “payload”. In the above URL, this “payload” is {"message":"Hello Data"}
, but I would modify it to {"x": 1.2, "y":2.3}
.
Alternatively, if there was some way to pass a float via URL, such as http://127.0.0.1/fsum/1.2/2.3
this would also solve my problem.
Thanks for the details. I did some digging.
Indeed, there is no logic to handle POST requests with Content-type: application/json
although it should be straightforward (it should check the header, extract the body of the request and parse it as JSON). I will add this.
For the other issue, passing floats into the URL, it seems you stumbled onto a bug. Normally something like this should have worked:
Base.convert(::Type{Float64}, s::String) = parse(Float64, s)
route("/somefloats/:x::Float64/:y::Float64") do
"x+y is $(@params(x) + @params(y))"
end
But it ends up in a 404 error - so the route matching is getting confused by something.
I’m afraid that until I fix this, the only option is to pass the values over GET:
route("/somefloats") do
"x+y is $(parse(Float64, @params(:x)) + parse(Float64, @params(:y)))"
end
and request: http://localhost:8000/somefloats?x=2.5&y=4.5
You could also send a payload as text over GET or POST (as application/x-www-form-urlencoded) and parse the text payload as JSON on the server-side.
I hope this helps.
Do you have a few minutes to open issues on Github for these 2 problems please, so I can look into them these days? Thanks
1 Like
hi, this is another example using Bukdu v0.4.1 (tagged today)
# Bukdu v0.4.1
using Bukdu
struct RESTController <: ApplicationController
conn::Conn
end
function create(c::RESTController)
@info :payload (c.params.message, c.params.x, c.params.y)
render(JSON, "OK")
end
routes() do
post("/messages", RESTController, create)
plug(Plug.Parsers, parsers=[:json])
end
Bukdu.start(8080)
#=
curl -H "Content-Type: application/json" http://127.0.0.1:8080/messages -d '{"message": "Hello Data"}'
curl -H "Content-Type: application/json" http://127.0.0.1:8080/messages -d '{"x": 1.2, "y": 2.3}'
=#
3 Likes
I tried to open some informative issues!
1 Like
Awesome @Seanny123, thank you very much!
2 Likes