
Kami terus mempertimbangkan teknologi Julia. Dan hari ini kita akan berbicara tentang paket yang dirancang untuk membangun layanan web. Bukan rahasia lagi bahwa niche utama bahasa Julia adalah komputasi berkinerja tinggi. Oleh karena itu, langkah yang cukup logis adalah secara langsung membuat layanan web yang mampu melakukan perhitungan sesuai permintaan. Tentu saja, layanan web bukan satu-satunya cara untuk berkomunikasi dalam lingkungan jaringan. Tapi, karena mereka sekarang paling banyak digunakan dalam sistem terdistribusi, maka kami akan mempertimbangkan pembuatan layanan yang melayani permintaan HTTP.
Perhatikan bahwa karena pemuda Julia, ada satu set paket yang bersaing. Karena itu, kami akan mencoba mencari tahu bagaimana dan mengapa menggunakannya. Sepanjang jalan, kami membandingkan implementasi layanan web JSON yang sama dengan bantuan mereka.
Infrastruktur Julia telah aktif berkembang dalam satu atau dua tahun terakhir. Dan, dalam hal ini, ini bukan hanya frase on-line yang ditulis untuk awal teks yang indah, tetapi penekanan pada kenyataan bahwa semuanya berubah secara intensif, dan apa yang relevan beberapa tahun yang lalu sekarang sudah ketinggalan zaman. Namun, kami akan mencoba menyoroti paket-paket stabil dan memberikan rekomendasi tentang bagaimana mengimplementasikan layanan web dengan bantuan mereka. Untuk lebih jelasnya, kami akan membuat layanan web yang menerima permintaan POST dengan data JSON dalam format berikut:
{ "title": "something", "body": "something" }
Kami berasumsi bahwa layanan yang kami buat tidak TENANG. Tugas utama kami adalah untuk mempertimbangkan dengan tepat metode untuk menggambarkan rute dan meminta penangan.
Paket HTTP.jl
Paket ini adalah implementasi utama protokol HTTP di Julia dan secara bertahap mendapatkan fitur baru. Selain menerapkan struktur dan fungsi khas untuk mengeksekusi permintaan klien HTTP, paket ini juga mengimplementasikan fungsi untuk membuat server HTTP. Pada saat yang sama, seiring dengan perkembangannya, paket tersebut telah menerima fungsi-fungsi yang membuatnya cukup nyaman bagi programmer untuk mendaftarkan penangan dan, dengan demikian, membangun layanan yang khas. Juga, dalam versi terbaru, ada dukungan built-in untuk protokol WebSocket, implementasi yang sebelumnya dibuat sebagai bagian dari paket WebSocket.jl terpisah. Artinya, HTTP.jl, saat ini, dapat memenuhi sebagian besar kebutuhan seorang programmer. Mari kita lihat beberapa contoh secara lebih rinci.
Klien HTTP
Kami memulai implementasi dengan kode klien, yang akan kami gunakan untuk memverifikasi operabilitas.
Paket HTTP menyediakan metode yang cocok dengan nama-nama perintah protokol HTTP. Dalam hal ini, kami menggunakan get
dan post
. Argumen opsional bernama verbose
memungkinkan Anda untuk mengatur jumlah informasi debug yang akan dikeluarkan. Jadi, misalnya, verbose=1
akan menghasilkan:
GET /user/Jemand HTTP/1.1 HTTP/1.1 200 OK <= (GET /user/Jemand HTTP/1.1)
Dan dalam kasus verbose=3
kami sudah mendapatkan set lengkap data yang dikirim dan diterima:
DEBUG: 2019-04-21T22:40:40.961 eb4f ️-> "POST /resource/process HTTP/1.1\r\n" (write) DEBUG: 2019-04-21T22:40:40.961 eb4f ️-> "Content-Type: application/json\r\n" (write) DEBUG: 2019-04-21T22:40:40.961 eb4f ️-> "Host: 127.0.0.1\r\n" (write) DEBUG: 2019-04-21T22:40:40.961 eb4f ️-> "Content-Length: 67\r\n" (write) DEBUG: 2019-04-21T22:40:40.961 eb4f ️-> "\r\n" (write) DEBUG: 2019-04-21T22:40:40.961 e1c6 ️-> "{\"title\":\"Some document\",\"body\":\"Test document with some content.\"}" (unsafe_write) DEBUG: 2019-04-21T22:40:40.963 eb4f ️<- "HTTP/1.1 200 OK\r\n" (readuntil) DEBUG: "Content-Type: application/json\r\n" DEBUG: "Transfer-Encoding: chunked\r\n" DEBUG: "\r\n" DEBUG: 2019-04-21T22:40:40.963 eb4f ️<- "5d\r\n" (readuntil) DEBUG: 2019-04-21T22:40:40.963 eb4f ️<- "{\"body\":\"Test document with some content.\",\"server_mark\":\"confirmed\",\"title\":\"Some document\"}" (unsafe_read) DEBUG: 2019-04-21T22:40:40.968 eb4f ️<- "\r\n" (readuntil) DEBUG: "0\r\n" DEBUG: 2019-04-21T22:40:40.968 eb4f ️<- "\r\n" (readuntil)
Di masa mendatang, kami hanya akan menggunakan verbose=1
untuk hanya melihat informasi minimal tentang apa yang terjadi.
Beberapa komentar mengenai kode.
doc = Document("Some document", "Test document with some content.")
Karena kita sebelumnya mendeklarasikan struktur Dokumen (lebih lanjut, tidak dapat diubah), sebuah konstruktor tersedia untuknya secara default, argumen yang sesuai dengan bidang struktur yang dinyatakan. Untuk mengonversinya menjadi JSON, kami menggunakan paket JSON.jl
dan metode json(doc)
.
Perhatikan fragmen:
r = HTTP.post( "http://$(HOST):$(PORT)/resource/process", [("Content-Type" => "application/json")], json(doc); verbose=3)
Karena kami melewati JSON, Anda harus secara eksplisit menentukan jenis application/json
di header Content-Type
. Header diteruskan ke metode HTTP.post
(namun, seperti semua yang lain) menggunakan array (bertipe Vector, tetapi bukan Dict) yang berisi pasangan header nama - nilai.
Untuk tes kesehatan, kami akan melakukan tiga pertanyaan:
- DAPATKAN permintaan ke rute root;
- DAPATKAN permintaan dalam format / pengguna / nama, di mana nama adalah nama yang dikirimkan;
- Permintaan POST / sumber daya / proses dengan objek JSON berlalu. Kami berharap untuk menerima dokumen yang sama, tetapi dengan bidang
server_mark
ditambahkan.
Kami akan menggunakan kode klien ini untuk menguji semua opsi implementasi server.
Server HTTP
Setelah Anda mengetahui klien, saatnya untuk mulai mengimplementasikan server. Untuk memulainya, kami akan membuat layanan hanya dengan bantuan HTTP.jl
agar tetap sebagai opsi dasar, yang tidak memerlukan instalasi paket lain. Kami mengingatkan Anda bahwa semua paket lain HTTP.jl
menggunakan HTTP.jl
Dalam contoh, Anda harus memperhatikan kode berikut:
dump(req)
mencetak ke konsol segala sesuatu yang diketahui oleh objek. Termasuk tipe data, nilai, serta semua bidang bersarang dan nilainya. Metode ini berguna untuk penelitian pustaka dan debugging.
Tali
(m = match( r".*/user/([[:alpha:]]+)", req.target))
adalah ekspresi reguler yang mem-parsing rute yang didaftarkan pawang. Paket HTTP.jl
tidak menyediakan cara otomatis untuk mengidentifikasi pola dalam rute.
Di dalam process_resource
handler, kami mengurai JSON yang diterima oleh layanan.
message = JSON.parse(String(req.body))
Data diakses melalui bidang req.body
. Perhatikan bahwa data datang dalam format array byte. Oleh karena itu, untuk bekerja dengan mereka sebagai string, konversi eksplisit ke string dilakukan. Metode JSON.parse
adalah metode paket JSON.jl
yang deserializes data dan membangun objek. Karena objek dalam kasus ini adalah array asosiatif (Dict), kita dapat dengan mudah menambahkan kunci baru ke dalamnya. Tali
message["server_mark"] = "confirmed"
menambahkan kunci server_mark
dengan nilai confirmed
.
Layanan HTTP.serve(ROUTER, Sockets.localhost, 8080)
ketika garis HTTP.serve(ROUTER, Sockets.localhost, 8080)
.
Respons kontrol untuk layanan berdasarkan HTTP.jl (diperoleh saat menjalankan kode klien dengan verbose=1
):
GET / HTTP/1.1 HTTP/1.1 200 OK <= (GET / HTTP/1.1) Hello World GET /user/Jemand HTTP/1.1 HTTP/1.1 200 OK <= (GET /user/Jemand HTTP/1.1) Hello Jemand POST /resource/process HTTP/1.1 HTTP/1.1 200 OK <= (POST /resource/process HTTP/1.1) {"body":"Test document with some content.","server_mark":"confirmed","title":"Some document"}
Terhadap latar belakang informasi debug dengan verbose=1
, kita dapat dengan jelas melihat baris: Hello World
, Hello Jemand
, "server_mark":"confirmed"
.
Setelah melihat kode layanan, muncul pertanyaan alami - mengapa kita membutuhkan semua paket lain, jika semuanya sangat sederhana dalam HTTP. Ada jawaban yang sangat sederhana untuk ini. HTTP - memungkinkan mendaftar penangan dinamis, tetapi bahkan implementasi dasar membaca file gambar statis dari direktori memerlukan implementasi terpisah. Oleh karena itu, kami juga mempertimbangkan paket yang berfokus pada pembuatan aplikasi web.
Paket Mux.jl
Paket ini diposisikan sebagai lapisan menengah untuk aplikasi web yang diimplementasikan pada Julia. Implementasinya sangat ringan. Tujuan utamanya adalah untuk menyediakan cara mudah untuk menggambarkan penangan. Ini bukan untuk mengatakan bahwa proyek ini tidak berkembang, tetapi berkembang perlahan. Namun, lihat kode untuk layanan kami yang melayani rute yang sama.
Di sini rute dijelaskan menggunakan metode page
. Aplikasi web dinyatakan menggunakan makro @app
. Argumen untuk metode page
adalah rute dan pawang. Pawang dapat ditentukan sebagai fungsi yang menerima permintaan sebagai input, atau dapat ditentukan sebagai fungsi lambda. Dari fungsi-fungsi tambahan yang bermanfaat, Mux.notfound()
hadir untuk mengirim respons Not found
ditentukan. Dan hasil yang harus dikirim ke klien tidak perlu dikemas dalam HTTP.Response
, seperti yang kita lakukan pada contoh sebelumnya, karena Mux akan melakukannya sendiri. Namun, Anda masih harus melakukan JSON parsing sendiri, seperti halnya serialisasi objek untuk respons - JSON.json(message)
.
message = JSON.parse(String(req[:data])) message["server_mark"] = "confirmed" return Dict( :body => JSON.json(message), :headers => [("Content-Type" => "application/json")] )
Respons dikirim sebagai array asosiatif dengan bidang :body
:headers
.
Memulai server dengan metode serve(test, 8080)
adalah asinkron, jadi salah satu opsi di Julia untuk mengatur menunggu penyelesaian adalah dengan memanggil kode:
Base.JLOptions().isinteractive == 0 && wait()
Jika tidak, layanan ini melakukan hal yang sama dengan versi sebelumnya di HTTP.jl
Kontrol respons untuk layanan:
GET / HTTP/1.1 HTTP/1.1 200 OK <= (GET / HTTP/1.1) <h1>Hello World!</h1> GET /user/Jemand HTTP/1.1 HTTP/1.1 200 OK <= (GET /user/Jemand HTTP/1.1) <h1>Hello, Jemand!</h1> POST /resource/process HTTP/1.1 HTTP/1.1 200 OK <= (POST /resource/process HTTP/1.1) {"body":"Test document with some content.","server_mark":"confirmed","title":"Some document"}
Paket Bukdu.jl
Paket ini dikembangkan di bawah pengaruh kerangka Phoenix, yang, pada gilirannya, diimplementasikan pada Elixir dan merupakan implementasi ide membangun web dari komunitas Ruby dalam proyeksi pada Elixir. Proyek ini berkembang cukup aktif dan diposisikan sebagai alat untuk membuat API tenang dan aplikasi web ringan. Ada beberapa fungsi untuk menyederhanakan serialisasi dan deserialisasi JSON. Ini tidak ada dalam HTTP.jl
dan Mux.jl
Mari kita lihat implementasi dari layanan web kami.
Hal pertama yang harus Anda perhatikan adalah deklarasi struktur untuk menyimpan status pengontrol.
struct WelcomeController <: ApplicationController conn::Conn end
Dalam hal ini, ini adalah tipe konkret yang dibuat sebagai turunan dari ApplicationController
tipe abstrak.
Metode untuk pengontrol dideklarasikan dengan cara yang sama sehubungan dengan implementasi sebelumnya. Ada sedikit perbedaan dalam penangan objek JSON kami.
function process_resource(c::WelcomeController) message = JSON.parse(String(c.conn.request.body)) @info message message["server_mark"] = "confirmed" render(JSON, message) end
Seperti yang Anda lihat, deserialisasi juga dilakukan secara independen menggunakan metode JSON.parse
, tetapi metode render(JSON, message)
yang JSON.parse
digunakan untuk membuat serialisasi respons.
Deklarasi rute dilakukan dengan gaya tradisional untuk para rubis, termasuk penggunaan blok do...end
.
routes() do get("/", WelcomeController, index) get("/user/:user", WelcomeController, welcome_user, :user => String) post("/resource/process", WelcomeController, process_resource) end
Juga, dengan cara tradisional untuk rubis, segmen dideklarasikan di jalur rute /user/:user
. Dengan kata lain, bagian variabel dari ekspresi, akses yang dapat dilakukan oleh nama yang ditentukan dalam templat. Secara sintaksis ditunjuk sebagai perwakilan dari Symbol
tipe. Ngomong-ngomong, bagi Julia, jenis Symbol
berarti, pada intinya, sama seperti untuk Ruby - ini adalah string yang tidak dapat diubah, direpresentasikan dalam memori dengan satu instance.
Dengan demikian, setelah kami menyatakan rute dengan bagian variabel, dan juga menunjukkan jenis bagian variabel ini, kami dapat merujuk ke data yang sudah diuraikan oleh nama yang diberikan. Dalam metode yang memproses permintaan, kami cukup mengakses bidang melalui titik di formulir c.params.user
.
welcome_user(c::WelcomeController) = render(JSON, "Hello " * c.params.user)
Kontrol respons untuk layanan:
GET / HTTP/1.1 HTTP/1.1 200 OK <= (GET / HTTP/1.1) "Hello World" GET /user/Jemand HTTP/1.1 HTTP/1.1 200 OK <= (GET /user/Jemand HTTP/1.1) "Hello Jemand" POST /resource/process HTTP/1.1 HTTP/1.1 200 OK <= (POST /resource/process HTTP/1.1) {"body":"Test document with some content.","server_mark":"confirmed","title":"Some document"}
Kesimpulan layanan ke konsol:
>./bukdu_json.jl INFO: Bukdu Listening on 127.0.0.1:8080 INFO: GET WelcomeController index 200 / INFO: GET WelcomeController welcome_user 200 /user/Jemand INFO: Dict{String,Any}("body"=>"Test document with some content.","title"=>"Some document") INFO: POST WelcomeController process_resource200 /resource/process
Paket Genie.jl
Proyek ambisius yang diposisikan sebagai kerangka kerja MVC. Dalam pendekatannya, "Rails" pada Julia cukup jelas terlihat, termasuk struktur direktori yang dibuat oleh generator. Proyek ini sedang berkembang, namun, untuk alasan yang tidak diketahui, paket ini tidak termasuk dalam repositori paket Julia. Yaitu, instalasinya hanya dimungkinkan dari repositori git dengan perintah:
julia>]
Kode layanan kami di Genie adalah sebagai berikut (kami tidak menggunakan generator):
Di sini Anda harus memperhatikan format deklarasi.
route("/") do "Hello World!" end
Kode ini sangat akrab bagi programmer Ruby. Blok do...end
sebagai penangan dan rute sebagai argumen ke metode. Perhatikan bahwa untuk Julia kode ini dapat ditulis ulang dalam bentuk:
route(req -> "Hello World!", "/")
Artinya, fungsi pawang ada di tempat pertama, rute ada di tempat kedua. Tapi untuk kasus kami, mari kita tinggalkan gaya ruby.
Genie secara otomatis mengemas hasil eksekusi menjadi respons HTTP. Dalam kasus minimum, kita hanya perlu mengembalikan hasil dari tipe yang benar, misalnya String. Dari fasilitas tambahan, verifikasi otomatis format input dan analisisnya dilaksanakan. Misalnya, untuk JSON, Anda hanya perlu memanggil metode jsonpayload()
.
route("/resource/process", method = POST) do message = jsonpayload()
Perhatikan fragmen kode yang dikomentari di sini. Metode jsonpayload()
nothing
mengembalikan nothing
jika karena alasan tertentu format input tidak dikenali sebagai JSON. Perhatikan bahwa hanya untuk ini, tajuk [("Content-Type" => "application/json")]
ditambahkan ke klien HTTP kami, karena jika tidak, Genie bahkan tidak akan mulai mem-parsing data sebagai JSON. Jika sesuatu yang tidak dapat dipahami telah datang, akan berguna untuk melihat rawpayload()
untuk apa itu. Namun, karena ini hanya fase debugging, Anda tidak boleh meninggalkannya dalam kode.
Juga, Anda harus memperhatikan mengembalikan hasil dalam message |> json!
format message |> json!
. Metode json!(str)
diletakkan terakhir di dalam pipa di sini. Ini memberikan serialisasi data dalam format JSON, dan juga memastikan bahwa Genie menambahkan Content-Type
benar. Juga, perhatikan fakta bahwa kata return
dalam kebanyakan kasus dalam contoh di atas berlebihan. Julia, seperti Ruby, misalnya, selalu mengembalikan hasil operasi terakhir atau nilai dari ekspresi yang ditentukan terakhir. Artinya, kata return
adalah opsional.
Kemampuan Genie tidak berakhir di sana, tetapi kami tidak membutuhkannya untuk mengimplementasikan layanan web.
Kontrol respons untuk layanan:
GET / HTTP/1.1 HTTP/1.1 200 OK <= (GET / HTTP/1.1) Hello World! GET /user/Jemand HTTP/1.1 HTTP/1.1 200 OK <= (GET /user/Jemand HTTP/1.1) Hello Jemand POST /resource/process HTTP/1.1 HTTP/1.1 200 OK <= (POST /resource/process HTTP/1.1) {"body":"Test document with some content.","server_mark":"confirmed","title":"Some document"}
Kesimpulan layanan ke konsol:
>./genie_json.jl [ Info: Ready! 2019-04-24 17:18:51:DEBUG:Main: Web Server starting at http://127.0.0.1:8080 2019-04-24 17:18:51:DEBUG:Main: Web Server running at http://127.0.0.1:8080 2019-04-24 17:19:21:INFO:Main: / 200 2019-04-24 17:19:21:INFO:Main: /user/Jemand 200 2019-04-24 17:19:22:INFO:Main: /resource/process 200
Paket JuliaWebAPI.jl
Paket ini diposisikan sebagai lapisan menengah untuk membuat aplikasi web pada masa itu ketika HTTP.jl hanya perpustakaan yang mengimplementasikan protokol. Penulis paket ini juga mengimplementasikan generator kode server berdasarkan spesifikasi Swagger (OpenAPI dan http://editor.swagger.io/ ) - lihat proyek https://github.com/JuliaComputing/Swagger.jl , dan generator ini menggunakan JuliaWebAPI .jl. Namun, masalah dengan JuliaWebAPI.jl adalah bahwa ia tidak mengimplementasikan kemampuan untuk memproses tubuh permintaan (misalnya, JSON), dikirim ke server melalui permintaan POST. Penulis percaya bahwa melewati parameter dalam format kunci-nilai cocok untuk semua kesempatan ... Masa depan paket ini tidak jelas. Semua fungsinya sudah diimplementasikan dalam banyak paket lain, termasuk HTTP.jl. Paket Swagger.jl juga tidak lagi menggunakannya.
WebSockets.jl
Implementasi awal protokol WebSocket. Paket ini telah digunakan sejak lama sebagai implementasi utama dari protokol ini, namun, saat ini, implementasinya termasuk dalam paket HTTP.jl. Paket WebSockets.jl sendiri menggunakan HTTP.jl untuk membuat koneksi, namun sekarang, tidak layak menggunakannya dalam pengembangan baru. Ini harus dianggap sebagai paket untuk kompatibilitas.
Kesimpulan
Ulasan ini menunjukkan berbagai cara untuk mengimplementasikan layanan web di Julia. Cara termudah dan paling universal adalah dengan langsung menggunakan paket HTTP.jl. Juga, paket Bukdu.jl dan Genie.jl sangat berguna. Minimal, perkembangan mereka harus dipantau. Mengenai paket Mux.jl, keuntungannya sekarang dibubarkan dengan latar belakang HTTP.jl. Karena itu, pendapat pribadi bukan untuk menggunakannya. Genie.jl adalah kerangka kerja yang sangat menjanjikan. Namun, sebelum Anda mulai menggunakannya, Anda setidaknya harus memahami mengapa penulis tidak mendaftarkannya sebagai paket resmi.
Perhatikan bahwa kode deserialisasi JSON dalam contoh digunakan tanpa penanganan kesalahan. Dalam semua kasus kecuali Genie, perlu untuk menangani kesalahan penguraian dan memberi tahu pengguna tentang hal ini. Contoh kode untuk HTTP.jl:
local message = nothing local body = IOBuffer(HTTP.payload(req)) try message = JSON.parse(body) catch err @error err.msg return HTTP.Response(400, string(err.msg)) end
Secara umum, kita dapat mengatakan bahwa sudah ada cukup dana untuk membuat layanan web di Julia. Artinya, tidak perlu "menemukan kembali roda" untuk menulisnya. Langkah selanjutnya adalah mengevaluasi bagaimana Julia dapat menahan beban dalam tolok ukur yang ada, jika seseorang siap untuk mengambilnya. Namun, untuk saat ini marilah kita membahas ulasan ini.
Referensi