Membangun API Istirahat dengan Kerangka Botol
Diterbitkan: 2022-03-11REST API telah menjadi cara umum untuk membuat antarmuka antara back-end dan front-end web, dan antara layanan web yang berbeda. Kesederhanaan antarmuka semacam ini, dan dukungan protokol HTTP dan HTTPS di mana-mana di berbagai jaringan dan kerangka kerja, menjadikannya pilihan yang mudah ketika mempertimbangkan masalah interoperabilitas.
Botol adalah kerangka web Python minimalis. Ini ringan, cepat, dan mudah digunakan, dan sangat cocok untuk membangun layanan RESTful. Perbandingan sederhana yang dibuat oleh Andriy Kornatskyy menempatkannya di antara tiga kerangka kerja teratas dalam hal waktu respons dan throughput (permintaan per detik). Dalam pengujian saya sendiri pada server virtual yang tersedia dari DigitalOcean, saya menemukan bahwa kombinasi tumpukan server uWSGI dan Botol dapat mencapai overhead 140-an per permintaan.
Pada artikel ini, saya akan memberikan panduan tentang cara membangun layanan RESTful API menggunakan Bottle.
Instalasi dan Konfigurasi
Kerangka Botol mencapai kinerja yang mengesankan sebagian berkat bobotnya yang ringan. Bahkan seluruh perpustakaan didistribusikan sebagai modul satu file. Ini berarti bahwa itu tidak memegang tangan Anda sebanyak kerangka kerja lainnya, tetapi juga lebih fleksibel dan dapat disesuaikan agar sesuai dengan banyak tumpukan teknologi yang berbeda. Oleh karena itu, Bottle paling cocok untuk proyek-proyek di mana kinerja dan kemampuan penyesuaian berada pada tingkat premium, dan di mana keuntungan penghematan waktu dari kerangka kerja yang lebih berat kurang menjadi pertimbangan.
Fleksibilitas Botol membuat deskripsi mendalam tentang pengaturan platform sedikit sia-sia, karena mungkin tidak mencerminkan tumpukan Anda sendiri. Namun, ikhtisar singkat tentang opsi, dan tempat untuk mempelajari lebih lanjut tentang cara menyiapkannya, sesuai di sini:
Instalasi
Memasang Botol semudah memasang paket Python lainnya. Pilihan Anda adalah:
- Instal di sistem Anda menggunakan manajer paket sistem. Debian Jessie (stabil saat ini) mengemas versi 0.12 sebagai python-bottle .
- Instal di sistem Anda menggunakan Python Package Index dengan
pip install bottle
. - Instal di lingkungan virtual (disarankan).
Untuk menginstal Botol di lingkungan virtual, Anda memerlukan alat virtualenv dan pip . Untuk menginstalnya, silakan merujuk ke dokumentasi virtualenv dan pip, meskipun Anda mungkin sudah memilikinya di sistem Anda.
Di Bash, buat lingkungan dengan Python 3:
$ virtualenv -p `which python3` env
Menekan parameter -p `which python3`
akan mengarah ke instalasi juru bahasa Python default yang ada di sistem – biasanya Python 2.7. Python 2.7 didukung, tetapi tutorial ini mengasumsikan Python 3.4.
Sekarang aktifkan lingkungan dan instal Botol:
$ . env/bin/activate $ pip install bottle
Itu dia. Botol sudah terpasang dan siap digunakan. Jika Anda tidak terbiasa dengan virtualenv atau pip , dokumentasi mereka adalah yang terbaik. Lihatlah! Mereka sangat berharga.
Server
Botol sesuai dengan Web Server Gateway Interface (WSGI) standar Python, yang berarti dapat digunakan dengan server yang sesuai dengan WSGI. Ini termasuk uWSGI, Tornado, Gunicorn, Apache, Amazon Beanstalk, Google App Engine, dan lainnya.
Cara yang benar untuk mengaturnya sedikit berbeda di setiap lingkungan. Botol memperlihatkan objek yang sesuai dengan antarmuka WSGI, dan server harus dikonfigurasi untuk berinteraksi dengan objek ini.
Untuk mempelajari lebih lanjut tentang cara menyiapkan server Anda, lihat dokumen server, dan ke dokumen Bottle, di sini.
Basis Data
Botol adalah database-agnostik dan tidak peduli dari mana data itu berasal. Jika Anda ingin menggunakan database di aplikasi Anda, Python Package Index memiliki beberapa opsi menarik, seperti SQLAlchemy, PyMongo, MongoEngine, CouchDB, dan Boto untuk DynamoDB. Anda hanya memerlukan adaptor yang sesuai untuk membuatnya bekerja dengan database pilihan Anda.
Dasar-dasar Kerangka Botol
Sekarang, mari kita lihat cara membuat aplikasi dasar di Botol. Untuk contoh kode, saya akan menganggap Python >= 3.4. Namun, sebagian besar dari apa yang akan saya tulis di sini akan bekerja pada Python 2.7 juga.
Aplikasi dasar di Botol terlihat seperti ini:
import bottle app = application = bottle.default_app() if __name__ == '__main__': bottle.run(host = '127.0.0.1', port = 8000)
Ketika saya mengatakan dasar, maksud saya program ini bahkan tidak "Hello World" Anda. (Kapan terakhir kali Anda mengakses antarmuka REST yang menjawab “Hello World?”) Semua permintaan HTTP ke 127.0.0.1:8000
akan menerima status respons 404 Not Found.
Aplikasi di Botol
Botol mungkin memiliki beberapa contoh aplikasi yang dibuat, tetapi demi kenyamanan, contoh pertama dibuat untuk Anda; itu aplikasi bawaan. Botol menyimpan instans ini dalam tumpukan internal ke modul. Setiap kali Anda melakukan sesuatu dengan Botol (seperti menjalankan aplikasi atau melampirkan rute) dan tidak menentukan aplikasi mana yang sedang Anda bicarakan, itu merujuk ke aplikasi default. Sebenarnya, baris app = application = bottle.default_app()
bahkan tidak perlu ada di aplikasi dasar ini, tetapi ada sehingga kita dapat dengan mudah menjalankan aplikasi default dengan Gunicorn, uWSGI atau beberapa server WSGI generik.
Kemungkinan beberapa aplikasi mungkin tampak membingungkan pada awalnya, tetapi mereka menambah fleksibilitas untuk Botol. Untuk modul aplikasi yang berbeda, Anda dapat membuat aplikasi Botol khusus dengan membuat instance kelas Botol lain dan mengaturnya dengan konfigurasi yang berbeda sesuai kebutuhan. Aplikasi yang berbeda ini dapat diakses oleh URL yang berbeda, melalui router URL Botol. Kami tidak akan membahasnya dalam tutorial ini, tetapi Anda dianjurkan untuk mempelajari dokumentasi Bottle di sini dan di sini.
Panggilan Server
Baris terakhir skrip menjalankan Botol menggunakan server yang ditunjukkan. Jika tidak ada server yang ditunjukkan, seperti yang terjadi di sini, server default adalah server referensi WSGI bawaan Python, yang hanya cocok untuk tujuan pengembangan. Server yang berbeda dapat digunakan seperti ini:
bottle.run(server='gunicorn', host = '127.0.0.1', port = 8000)
Ini adalah gula sintaksis yang memungkinkan Anda memulai aplikasi dengan menjalankan skrip ini. Misalnya, jika file ini bernama main.py
, Anda cukup menjalankan python main.py
untuk memulai aplikasi. Botol membawa daftar adaptor server yang cukup luas yang dapat digunakan dengan cara ini.
Beberapa server WSGI tidak memiliki adaptor Botol. Ini dapat dimulai dengan menjalankan perintah server sendiri. Di uWSGI, misalnya, yang harus Anda lakukan hanyalah memanggil uwsgi
seperti ini:
$ uwsgi --http :8000 --wsgi-file main.py
Catatan tentang Struktur File
Botol menyerahkan struktur file aplikasi Anda sepenuhnya kepada Anda. Saya telah menemukan kebijakan struktur file saya berkembang dari proyek ke proyek, tetapi cenderung didasarkan pada filosofi MVC.
Membangun REST API Anda
Tentu saja, tidak ada yang membutuhkan server yang hanya mengembalikan 404 untuk setiap URI yang diminta. Saya telah berjanji kepada Anda bahwa kami akan membangun REST API, jadi mari kita lakukan.
Misalkan Anda ingin membangun antarmuka yang memanipulasi sekumpulan nama. Dalam aplikasi nyata Anda mungkin akan menggunakan database untuk ini, tetapi untuk contoh ini kita hanya akan menggunakan struktur data set
dalam memori.
Kerangka API kami mungkin terlihat seperti ini. Anda dapat menempatkan kode ini di mana saja dalam proyek, tetapi rekomendasi saya adalah file API terpisah, seperti api/names.py
.
from bottle import request, response from bottle import post, get, put, delete _names = set() # the set of names @post('/names') def creation_handler(): '''Handles name creation''' pass @get('/names') def listing_handler(): '''Handles name listing''' pass @put('/names/<name>') def update_handler(name): '''Handles name updates''' pass @delete('/names/<name>') def delete_handler(name): '''Handles name deletions''' pass
Rute
Seperti yang kita lihat, perutean di Botol dilakukan menggunakan dekorator. Dekorator yang diimpor post
, get
, put
, dan delete
register handler untuk empat tindakan ini. Memahami cara kerja tersebut dapat dirinci sebagai berikut:
- Semua dekorator di atas adalah jalan pintas ke dekorator perutean
default_app
. Misalnya, dekorator@get()
menerapkanbottle.default_app().get()
ke handler. - Metode perutean pada
default_app
semuanya adalah pintasan untukroute()
. Jadidefault_app().get('/')
sama dengandefault_app().route(method='GET', '/')
.
Jadi @get('/')
sama dengan @route(method='GET', '/')
, yang sama dengan @bottle.default_app().route(method='GET', '/')
, dan ini dapat digunakan secara bergantian.
Satu hal yang berguna tentang dekorator @route
adalah jika Anda ingin, misalnya, menggunakan handler yang sama untuk menangani pembaruan dan penghapusan objek, Anda bisa meneruskan daftar metode yang ditanganinya seperti ini:
@route('/names/<name>', method=['PUT', 'DELETE']) def update_delete_handler(name): '''Handles name updates and deletions''' pass
Baiklah, mari kita terapkan beberapa penangan ini.
POST: Penciptaan Sumber Daya
Handler POST kami mungkin terlihat seperti ini:
import re, json namepattern = re.compile(r'^[a-zA-Z\d]{1,64}$') @post('/names') def creation_handler(): '''Handles name creation''' try: # parse input data try: data = request.json() except: raise ValueError if data is None: raise ValueError # extract and validate name try: if namepattern.match(data['name']) is None: raise ValueError name = data['name'] except (TypeError, KeyError): raise ValueError # check for existence if name in _names: raise KeyError except ValueError: # if bad request data, return 400 Bad Request response.status = 400 return except KeyError: # if name already exists, return 409 Conflict response.status = 409 return # add name _names.add(name) # return 200 Success response.headers['Content-Type'] = 'application/json' return json.dumps({'name': name})
Yah, itu cukup banyak. Mari kita tinjau langkah-langkah ini bagian demi bagian.
Penguraian Tubuh
API ini mengharuskan pengguna untuk POST string JSON di badan dengan atribut bernama "nama".

Objek request
yang diimpor sebelumnya dari bottle
selalu menunjuk ke permintaan saat ini dan menyimpan semua data permintaan. Atribut body
-nya berisi aliran byte dari badan permintaan, yang dapat diakses oleh fungsi apa pun yang dapat membaca objek aliran (seperti membaca file).
Metode request.json()
memeriksa header permintaan untuk tipe konten "application/json" dan mem-parsing isi jika sudah benar. Jika Botol mendeteksi tubuh yang salah bentuk (misalnya: kosong atau dengan tipe konten yang salah), metode ini mengembalikan None
dan dengan demikian kita menaikkan ValueError
. Jika konten JSON yang cacat terdeteksi oleh parser JSON; itu memunculkan pengecualian yang kami tangkap dan bangkitkan, sekali lagi sebagai ValueError
.
Penguraian dan Validasi Objek
Jika tidak ada kesalahan, kami telah mengonversi isi permintaan menjadi objek Python yang direferensikan oleh variabel data
. Jika kami telah menerima kamus dengan kunci “nama”, kami akan dapat mengaksesnya melalui data['name']
. Jika kami menerima kamus tanpa kunci ini, mencoba mengaksesnya akan membawa kami ke pengecualian KeyError
. Jika kami menerima apa pun selain kamus, kami akan mendapatkan pengecualian TypeError
. Jika salah satu dari kesalahan ini terjadi, sekali lagi, kami menaikkannya sebagai ValueError
, yang menunjukkan input yang buruk.
Untuk memeriksa apakah kunci nama memiliki format yang benar, kita harus mengujinya terhadap topeng regex, seperti topeng namepattern
yang kita buat di sini. Jika name
kunci bukan string, namepattern.match()
akan memunculkan TypeError
, dan jika tidak cocok akan mengembalikan None
.
Dengan mask dalam contoh ini, nama harus berupa alfanumerik ASCII tanpa kosong dari 1 hingga 64 karakter. Ini adalah validasi sederhana dan tidak menguji objek dengan data sampah, misalnya. Validasi yang lebih kompleks dan lengkap dapat dicapai melalui penggunaan alat seperti FormEncode.
Menguji Keberadaan
Tes terakhir sebelum memenuhi permintaan adalah apakah nama yang diberikan sudah ada di set. Dalam aplikasi yang lebih terstruktur, pengujian itu mungkin harus dilakukan oleh modul khusus dan memberi sinyal ke API kami melalui pengecualian khusus, tetapi karena kami memanipulasi satu set secara langsung, kami harus melakukannya di sini.
Kami memberi sinyal keberadaan nama tersebut dengan memunculkan KeyError
.
Tanggapan Kesalahan
Sama seperti objek permintaan menyimpan semua data permintaan, objek respons melakukan hal yang sama untuk data respons. Ada dua cara untuk menyetel status respons:
response.status = 400
dan:
response.status = '400 Bad Request'
Untuk contoh kami, kami memilih formulir yang lebih sederhana, tetapi formulir kedua dapat digunakan untuk menentukan deskripsi teks kesalahan. Secara internal, Botol akan membagi string kedua dan mengatur kode numerik dengan tepat.
Respon Sukses
Jika semua langkah berhasil, kami memenuhi permintaan dengan menambahkan nama ke set _names
, menyetel header respons Content-Type
, dan mengembalikan respons. String apa pun yang dikembalikan oleh fungsi akan diperlakukan sebagai badan respons dari respons 200 Success
, jadi kami cukup membuatnya dengan json.dumps
.
DAPATKAN: Daftar Sumber Daya
Beranjak dari pembuatan nama, kami akan menerapkan pengendali daftar nama:
@get('/names') def listing_handler(): '''Handles name listing''' response.headers['Content-Type'] = 'application/json' response.headers['Cache-Control'] = 'no-cache' return json.dumps({'names': list(_names)})
Daftar nama jauh lebih mudah, bukan? Dibandingkan dengan pembuatan nama, tidak banyak yang bisa dilakukan di sini. Cukup atur beberapa tajuk respons dan kembalikan representasi JSON dari semua nama, dan selesai.
PUT: Pembaruan Sumber Daya
Sekarang, mari kita lihat bagaimana mengimplementasikan metode update. Ini tidak jauh berbeda dari metode create, tetapi kami menggunakan contoh ini untuk memperkenalkan parameter URI.
@put('/names/<oldname>') def update_handler(name): '''Handles name updates''' try: # parse input data try: data = json.load(utf8reader(request.body)) except: raise ValueError # extract and validate new name try: if namepattern.match(data['name']) is None: raise ValueError newname = data['name'] except (TypeError, KeyError): raise ValueError # check if updated name exists if oldname not in _names: raise KeyError(404) # check if new name exists if name in _names: raise KeyError(409) except ValueError: response.status = 400 return except KeyError as e: response.status = e.args[0] return # add new name and remove old name _names.remove(oldname) _names.add(newname) # return 200 Success response.headers['Content-Type'] = 'application/json' return json.dumps({'name': newname})
Skema isi untuk tindakan pembaruan sama seperti untuk tindakan pembuatan, tetapi sekarang kami juga memiliki parameter nama lama baru di URI, seperti yang didefinisikan oleh route oldname
@put('/names/<oldname>')
.
Parameter URI
Seperti yang Anda lihat, notasi Botol untuk parameter URI sangat mudah. Anda dapat membuat URI dengan parameter sebanyak yang Anda inginkan. Botol secara otomatis mengekstraknya dari URI dan meneruskannya ke pengendali permintaan:
@get('/<param1>/<param2>') def handler(param1, param2): pass
Menggunakan dekorator rute berjenjang, Anda dapat membangun URI dengan parameter opsional:
@get('/<param1>') @get('/<param1>/<param2>') def handler(param1, param2 = None) pass
Selain itu, Botol memungkinkan filter perutean berikut di URI:
-
int
Hanya cocok dengan parameter yang dapat dikonversi ke
int
, dan meneruskan nilai yang dikonversi ke handler:@get('/<param:int>') def handler(param): pass
-
float
Sama seperti
int
, tetapi dengan nilai floating point:@get('/<param:float>') def handler(param): pass
-
re
(ekspresi reguler)
Hanya cocok dengan parameter yang cocok dengan ekspresi reguler yang diberikan:
@get('/<param:re:^[az]+$>') def handler(param): pass
-
path
Mencocokkan subsegmen jalur URI dengan cara yang fleksibel:
@get('/<param:path>/id>') def handler(param): pass
Pertandingan:
/x/id
, meneruskanx
sebagaiparam
./x/y/id
, meneruskanx/y
sebagaiparam
.
HAPUS: Penghapusan Sumber Daya
Seperti metode GET, metode DELETE memberi kita sedikit berita. Perhatikan saja bahwa mengembalikan None
tanpa menyetel status mengembalikan respons dengan isi kosong dan 200 kode status.
@delete('/names/<name>') def delete_handler(name): '''Handles name updates''' try: # Check if name exists if name not in _names: raise KeyError except KeyError: response.status = 404 return # Remove name _names.remove(name) return
Langkah Terakhir: Mengaktifkan API
Misalkan kita telah menyimpan nama API kita sebagai api/names.py
, sekarang kita dapat mengaktifkan rute-rute ini di file aplikasi utama main.py
.
import bottle from api import names app = application = bottle.default_app() if __name__ == '__main__': bottle.run(host = '127.0.0.1', port = 8000)
Perhatikan bahwa kami hanya mengimpor modul names
. Karena kami telah mendekorasi semua metode dengan URI yang dilampirkan ke aplikasi default, tidak perlu melakukan penyiapan lebih lanjut. Metode kami sudah ada, siap diakses.
Anda dapat menggunakan alat seperti Curl atau Postman untuk menggunakan API dan mengujinya secara manual. (Jika Anda menggunakan Curl, Anda dapat menggunakan formatter JSON untuk membuat respons terlihat tidak terlalu berantakan.)
Bonus: Berbagi Sumber Daya Lintas Asal (CORS)
Salah satu alasan umum untuk membangun REST API adalah untuk berkomunikasi dengan front-end JavaScript melalui AJAX. Untuk beberapa aplikasi, permintaan ini harus diizinkan untuk datang dari domain apa pun, bukan hanya domain asal API Anda. Secara default, sebagian besar browser melarang perilaku ini, jadi izinkan saya menunjukkan cara menyiapkan berbagi sumber daya lintas-asal (CORS) di Botol untuk mengizinkan ini:
from bottle import hook, route, response _allow_origin = '*' _allow_methods = 'PUT, GET, POST, DELETE, OPTIONS' _allow_headers = 'Authorization, Origin, Accept, Content-Type, X-Requested-With' @hook('after_request') def enable_cors(): '''Add headers to enable CORS''' response.headers['Access-Control-Allow-Origin'] = _allow_origin response.headers['Access-Control-Allow-Methods'] = _allow_methods response.headers['Access-Control-Allow-Headers'] = _allow_headers @route('/', method = 'OPTIONS') @route('/<path:path>', method = 'OPTIONS') def options_handler(path = None): return
Dekorator hook
memungkinkan kita memanggil fungsi sebelum atau sesudah setiap permintaan. Dalam kasus kami, untuk mengaktifkan CORS, kami harus mengatur header Access-Control-Allow-Origin
, -Allow-Methods
dan -Allow-Headers
untuk setiap respons kami. Ini menunjukkan kepada pemohon bahwa kami akan melayani permintaan yang ditunjukkan.
Juga, klien dapat membuat permintaan HTTP OPSI ke server untuk melihat apakah itu benar-benar dapat membuat permintaan dengan metode lain. Dengan contoh contoh penampung-semua ini, kami menanggapi semua permintaan OPTIONS dengan kode status 200 dan isi kosong.
Untuk mengaktifkan ini, simpan saja dan impor dari modul utama.
Bungkus
Itu saja!
Dengan tutorial ini, saya telah mencoba membahas langkah-langkah dasar untuk membuat REST API untuk aplikasi Python dengan kerangka web Bottle.
Anda dapat memperdalam pengetahuan Anda tentang kerangka kerja kecil namun kuat ini dengan mengunjungi tutorial dan dokumen referensi API-nya.