Pengungkapan ini dibuat singkat karena hampir tidak relevan di sini: kami adalah Geonode dan kami menjual layanan proxy. Permintaan POST tidak memerlukan apa pun dari kami. Semua yang dijelaskan di bawah ini berjalan melalui koneksi Anda sendiri ke endpoint Anda sendiri. Proksi baru berperan jauh di kemudian hari, dan ada catatan singkat di bagian akhir mengenai satu hal yang benar-benar berubah saat Anda melakukan POST melalui proksi — yang ternyata tidak seperti yang diharapkan kebanyakan orang.
Dasar-dasar
curl -d "name=Ada&role=engineer" https://api.example.com/users
Panduan curl menjelaskan perintah ``-d, --data`
: "Mengirimkan data yang ditentukan ke server. Untuk HTTP(S), hal ini dilakukan dengan metode POST, sama seperti yang dilakukan browser saat pengguna telah mengisi formulir HTML dan menekan tombol kirim. Opsi ini membuat curl meneruskan data ke server menggunakan tipe konten application/x-www-form-urlencoded`
."
Ada dua hal yang terjadi secara otomatis dan keduanya penting.
**-d
secara implisit menggunakan metode POST.** Anda tidak perlu menggunakan -X POST
, dan menambahkan atribut tersebut tidak mengubah apa pun kecuali dalam penanganan pengalihan (redirect), di mana -X
diterapkan pada setiap lompatan (hop).
**-d
menetapkan Content-Type: application/x-www-form-urlencoded
.** Hal ini benar untuk pengiriman formulir, tetapi salah untuk hampir semua hal lainnya.
Anda dapat mengulangi -d
dan curl akan menggabungkan bagian-bagiannya: panduan manual menyebutkan bahwa "menggunakan -d name=daniel -d skill=lousy
akan menghasilkan potongan POST yang terlihat seperti name=daniel&skill=lousy
."
Mengirim JSON
Penggunaan nyata yang paling umum, dan di sinilah letak kesalahannya.
Cara eksplisit:
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada","role":"engineer"}'
Cara pintas yang belum banyak diketahui orang. curl memiliki opsi khusus --json, yang didokumentasikan sebagai: "Mengirim data JSON yang ditentukan dalam permintaan POST ke server HTTP. --json berfungsi sebagai jalan pintas untuk meneruskan tiga opsi berikut: --data-binary [arg], --header "Content-Type: application/json", --header "Accept: application/json"."
curl --json '{"name":"Ada","role":"engineer"}' https://api.example.com/users
Tiga opsi dalam satu, dan opsi ini menetapkan Accept serta Content-Type, yang biasanya sesuai dengan yang Anda inginkan. Opsi ini juga membaca dari berkas atau stdin dengan @:
curl --json @payload.json https://api.example.com/users
cat payload.json | curl --json @- https://api.example.com/users
Satu peringatan jujur dari manual: "Tidak ada verifikasi bahwa data yang dikirimkan benar-benar JSON atau bahwa sintaksnya benar." Perintah ini mengatur header; ia tidak melakukan validasi. Isi yang tidak valid tetap akan dikirim dengan tipe konten JSON, dan keluhan server akan berkaitan dengan JSON Anda, bukan dengan curl.
Header yang disetel "dapat diganti dengan --header seperti biasa", sehingga Anda dapat tetap menggunakan pintasan tersebut dan menyesuaikan salah satu bagiannya.
Tanda kutip tunggal sangat penting. Bungkus isi JSON dengan tanda kutip tunggal agar shell tidak mengembang $ atau menafsirkan tanda kutip ganda di dalamnya. Jika JSON Anda juga mengandung tanda kutip tunggal, simpanlah dalam berkas.
Lima Opsi Data dan Kapan Menggunakan Masing-Masing
Ini adalah bagian yang menghilangkan sebagian besar kebingungan, karena curl memiliki beberapa varian --data yang berbeda dalam hal-hal tertentu.
| Opsi | Tipe konten yang ditetapkan | Apakah @ khusus? | Baris baru | Digunakan untuk |
|---|---|---|---|---|
-d / --data | form-urlencoded | Ya, membaca berkas | Dihapus | Pengiriman formulir |
--data-raw | form-urlencoded | Tidak | Dihapus | Data yang dimulai dengan @ |
--data-binary | form-urlencoded | Ya | Dijaga | Berkas, byte persis |
--data-urlencode | form-urlencoded | Ya | Dienskripsi | Nilai dengan karakter khusus |
--json | application/json | Ya | Tetap utuh | Isi JSON |
--data-raw ada karena satu alasan: panduan tersebut menyatakan bahwa URL ini mengirimkan data "serupa dengan --data tetapi tanpa interpretasi khusus terhadap karakter @". Jika data literal Anda dimulai dengan @ — alamat email, nama pengguna, atau sebutan — -d akan mencoba membacanya sebagai nama file dan gagal dengan cara yang membingungkan. Contoh mereka sendiri adalah curl --data-raw "@at@at@".
--data-binary adalah yang harus digunakan untuk file. Panduan pengguna: "Kirim data persis seperti yang ditentukan tanpa pemrosesan tambahan apa pun... baris baru dan karakter carriage return dipertahankan, dan konversi tidak pernah dilakukan." Perhatikan bahwa secara default, sistem masih mengirim application/x-www-form-urlencoded, jadi jika Anda mengirim data biner sembarang, panduan menyarankan untuk menggantinya: -H "Content-Type: application/octet-stream".
Inilah mengapa -d @file.json dapat menyebabkan masalah tersembunyi: baris baru dihilangkan. Untuk JSON, hal ini biasanya tidak menjadi masalah; namun, untuk hal apa pun di mana spasi memiliki arti penting, hal ini menjadi masalah. --data-binary @file.json atau --json @file.json adalah bentuk yang lebih aman.
--data-urlencode menangani nilai yang mengandung &, =, spasi, atau apa pun yang dapat merusak pengkodean formulir. Panduan pengguna mendokumentasikan beberapa sintaksis, dan yang Anda butuhkan hampir selalu adalah name=content, yang mengenkode konten menggunakan URL-encoding dan membiarkan nama tetap utuh:
curl --data-urlencode "comment=hello & goodbye = fine" https://example.com/post
Tanpa itu, & akan dibaca sebagai pemisah bidang dan komentar Anda akan terpotong tanpa pemberitahuan. Ada juga name@filename, yang memuat konten dari berkas, mengenkode URL-nya, dan menambahkan = ke nama.
Unggahan Berkas dan Formulir Multipart
Untuk unggahan berkas yang sebenarnya, -F adalah opsi yang tepat, dan cara kerjanya berbeda dari -d.
Panduan: "-F, --form <name=content> ... meniru formulir yang telah diisi di mana pengguna telah menekan tombol kirim. Hal ini membuat curl mengirim data POST menggunakan Content-Type multipart/form-data sesuai dengan RFC 2388."
curl -F "file=@report.pdf" -F "title=Q3 Report" https://api.example.com/upload
Perbedaan antara @ dan < layak dipelajari karena tidak intuitif. Panduan: "Untuk memaksa bagian 'content' menjadi sebuah file, tambahkan awalan @ pada nama file. Untuk mengambil bagian 'content' dari sebuah file, tambahkan awalan < pada nama file. Perbedaan antara @ dan < adalah bahwa @ membuat file dilampirkan dalam postingan sebagai unggahan file, sedangkan < membuat bidang teks dan mengambil isi untuk bidang teks tersebut dari sebuah file."
Jadi, @ mengunggah file sebagai file; < mengirimkan isi file sebagai nilai bidang teks.
Untuk menetapkan jenis konten pada suatu bagian:
curl -F "file=@data.csv;type=text/csv" https://api.example.com/upload
Dan jika Anda memerlukan nilai literal yang diawali dengan @ atau <, gunakan --form-string, yang tidak menginterpretasikan kedua karakter tersebut.
Jangan atur Content-Type: multipart/form-data sendiri. curl akan menghasilkannya termasuk parameter batas, dan menggantinya akan menghasilkan permintaan yang tidak dapat diproses oleh server — penyebab yang sangat umum dari kode status 400 dan 415 yang tidak dapat dijelaskan.
Untuk melakukan PUT file secara langsung, -T lebih sederhana: "Unggah file lokal yang ditentukan ke URL jarak jauh... Jika opsi ini digunakan dengan URL HTTP(S), metode PUT akan digunakan."
Otentikasi dan Header
curl --json '{"a":1}' \
-H "Authorization: Bearer eyJhbG..." \
https://api.example.com/items
-H
dapat diterapkan berulang kali dan menggantikan pengaturan default curl, termasuk yang ditetapkan oleh --json
.
Untuk otentikasi dasar, gunakan -u user:password
— atau hanya -u user
, yang akan membuat curl menampilkan prompt sehingga kata sandi tidak tersimpan dalam riwayat shell Anda. Manual mencatat bahwa "pada sistem yang mendukungnya, curl menyembunyikan argumen opsi yang diberikan dari daftar proses", sambil menambahkan bahwa "hal ini tidak cukup untuk melindungi kredensial".
Untuk API berbasis sesi, tangkap dan gunakan kembali cookie:
curl -c jar.txt -d "user=ada&pass=secret" https://example.com/login
curl -b jar.txt --json '{"a":1}' https://example.com/api/items
Memecahkan Masalah POST yang Tidak Berfungsi
Langkah-langkah singkat yang dapat mengatasi hampir semua masalah.
Lihat dengan tepat apa yang Anda kirimkan:
curl -v --json '{"a":1}' https://api.example.com/items 2>&1 | grep -E '^[<>]'
Baris yang dimulai dengan >
adalah permintaan Anda, sedangkan <
adalah responsnya. Pastikan metode, Content-Type
, dan isi permintaan sesuai dengan yang Anda inginkan. Sebagian besar laporan "API-nya rusak" ternyata dapat diselesaikan di sini.
Baca kode status dan isi pesan kesalahan:
curl -sS -o body.txt -D headers.txt --json '{"a":1}' https://api.example.com/items
head -1 headers.txt; head -c 300 body.txt
Pahami kegagalan umum:
| Status | Biasanya berarti |
|---|---|
| 400 | Isi permintaan tidak valid atau bidang wajib tidak terisi |
| 401 | Kredensial tidak ada atau tidak valid |
| 403 | Telah diautentikasi tetapi tidak diizinkan |
| 405 | Titik akhir tidak menerima POST — periksa URL dan metode |
| 413 | Isi permintaan terlalu besar |
| 415 | Format data (Content-Type |
) salah — kesalahan klasik "-d | |
| " dengan JSON | |
| 422 | Tipe konten benar, tetapi data gagal validasi |
Perbedaan antara kode status 415 dan 422 perlu dipahami dengan baik: 415 berarti pembungkus (wrapper) salah, sedangkan 422 berarti isinya yang salah. Kami telah membahasnya secara mendetail dalam apa itu kode status 415.
Tampilkan kegagalan secara jelas dalam skrip:
curl --fail-with-body --silent --show-error \
--connect-timeout 5 --max-time 30 \
--json @payload.json https://api.example.com/items
--fail-with-body
menghasilkan nilai keluar non-nol pada kesalahan HTTP sambil tetap menampilkan isi respons, yang merupakan hal yang Anda inginkan ketika API mengembalikan pesan kesalahan JSON yang berguna. --fail
biasa mengabaikan isi respons, yang berarti membuang penjelasan yang diberikan.
Mengubah Permintaan Browser Menjadi Perintah curl
Cara tercepat untuk mereproduksi permintaan POST yang berfungsi di browser tetapi tidak di kode Anda — dan teknik yang biasanya baru diketahui orang jauh lebih lambat dari yang seharusnya.
Salin dari alat pengembang. Di Chrome, Firefox, dan Safari, buka tab Jaringan, temukan permintaan tersebut, klik kanan, lalu pilih "Salin sebagai cURL". Anda akan mendapatkan perintah lengkap beserta setiap header, cookie, dan isi yang dikirim oleh browser. Tempelkan ke terminal dan seharusnya berperilaku sama persis.
Hal ini langsung menjawab pertanyaan yang mendasari sebagian besar sesi debugging: apakah masalahnya ada pada permintaan, ataukah pada kode saya? Jika perintah yang disalin berfungsi sedangkan kode Anda tidak, perbedaannya terletak pada apa yang Anda kirimkan, dan kini Anda memiliki kedua versi tersebut berdampingan untuk dibandingkan.
Kemudian sederhanakan. Perintah yang disalin biasanya membawa tiga puluh header, sebagian besar di antaranya tidak relevan. Hapus header tersebut sedikit demi sedikit dan jalankan kembali hingga terjadi kesalahan. Yang tersisa adalah set minimum yang benar-benar dibutuhkan server, dan itulah yang seharusnya ada di aplikasi Anda:
curl 'https://api.example.com/items' \
-H 'content-type: application/json' \
-H 'authorization: Bearer eyJhbG...' \
--data-raw '{"name":"Ada"}'
Perhatikan bahwa browser mengirimkan --data-raw alih-alih -d, tepatnya karena isi (body) yang dimulai dengan @ akan disalahartikan sebagai nama file.
Perhatikan dua hal yang tidak akan bertahan saat disalin. Cookie disertakan sebagai header literal dan akan kedaluwarsa. Dan apa pun yang dihitung halaman dalam JavaScript — token CSRF, tanda tangan, nilai yang diturunkan dari cap waktu — disematkan ke dalam perintah yang disalin sebagai string tetap, sehingga berfungsi sekali dan kemudian berhenti. Jika permintaan yang direproduksi berhasil pada percobaan pertama tetapi gagal pada percobaan kedua, hampir selalu itulah penyebabnya, dan solusinya adalah mengambil token tersebut daripada mengkodekannya secara statis.
Sebaliknya, beberapa alat mengubah perintah curl menjadi kode untuk sebagian besar bahasa pemrograman, yang merupakan cara yang masuk akal untuk beralih dari perintah yang berfungsi ke klien yang berfungsi tanpa perlu mengetik ulang header secara manual.
Melakukan POST Melalui Proxy
Singkatnya, poin pentingnya adalah peringatan, bukan teknik.
curl -x http://user:pass@proxy.example.com:9000 \
--json '{"a":1}' https://api.example.com/items
Mekanismenya tetap sama. Yang berubah adalah perhitungan percobaan ulang, dan inilah bagian yang perlu dipertimbangkan sebelum Anda menambahkan --retry
.
POST umumnya tidak idempoten. Mengirimnya dua kali dapat menghasilkan dua catatan. Fitur --retry` ` pada `curl` secara default hanya aktif pada kondisi sementara, tetapi --retry-all-errors memperluas cakupannya secara signifikan — dan melalui proxy, kode status 5xx sering kali berarti target menolak permintaan Anda, bukan sekadar mengalami gangguan sesaat. Mencoba ulang hal tersebut, paling banter sia-sia dan paling buruk akan menduplikasi penulisan data.
Timeout bukanlah bukti kegagalan. Jika permintaan mengalami timeout setelah server menerimanya, operasi tersebut mungkin telah selesai sementara Anda melihat pesan kesalahan. Melalui proxy, ada satu hop tambahan di mana hal ini bisa terjadi. Jika operasi tersebut penting, gunakan kunci idempotensi — sebagian besar API yang serius mendukungnya — daripada mengandalkan logika percobaan ulang untuk keamanan.
Dan periksa hop mana yang menolak permintaan Anda. %{http_connect}
melaporkan respons proxy terhadap permintaan CONNECT secara terpisah dari status target:
curl -sS -o /dev/null -x "$PROXY" \
-w 'connect=%{http_connect} status=%{response_code}\n' \
--json '{"a":1}' https://api.example.com/items
connect=407
berarti proxy meminta kredensial. connect=200 status=403
berarti proxy berfungsi dengan baik, tetapi target yang menolak. Masalah yang berbeda, solusi yang berbeda.
Pertanyaan Terkait
Bagaimana cara mengirim permintaan POST dengan curl?
curl -d "key=value" URL. Opsi -d secara implisit menggunakan metode POST, sehingga -X POST tidak diperlukan. Opsi ini juga menetapkan Content-Type: application/x-www-form-urlencoded, yang tepat untuk pengiriman formulir tetapi salah untuk JSON.
Bagaimana cara mengirim JSON dengan metode POST menggunakan curl?
curl --json '{"key":"value"}' URL adalah cara singkatnya — opsi ini menetapkan --data-binary serta kedua header Content-Type dan Accept menjadi application/json. Bentuk yang lebih panjang adalah -X POST -H "Content-Type: application/json" -d '...'. Perhatikan bahwa --json tidak memvalidasi JSON Anda.
Mengapa saya mendapatkan kesalahan 415 Unsupported Media Type dari curl?
Hampir selalu karena Anda menggunakan -d dengan isi JSON tanpa menetapkan tipe konten. -d mengirimkan data dalam format form-encoded, dan API yang mengharapkan JSON akan menolaknya. Gunakan --json, atau tambahkan -H "Content-Type: application/json".
Apa perbedaan antara -d dan --data-raw?
-d menganggap @ di awal sebagai "baca dari berkas ini". --data-raw tidak demikian, sehingga opsi ini diperlukan saat data literal Anda dimulai dengan @ — misalnya, alamat email atau handle. Selain itu, keduanya berperilaku sama.
Bagaimana cara mengunggah berkas dengan curl POST?
Gunakan curl -F "file=@document.pdf" URL, yang mengirimkan multipart/form-data. Gunakan @ untuk melampirkan berkas sebagai berkas, dan < untuk mengirimkan isinya sebagai nilai bidang teks. Jangan atur header Content-Type secara manual — curl akan menghasilkannya dengan batas yang diperlukan.
Bagaimana cara mengirim data POST dari sebuah file?
Gunakan curl --json @payload.json URL untuk JSON, atau --data-binary @file untuk byte persis termasuk baris baru. Hindari -d @file jika spasi kosong berpengaruh, karena -d akan menghapus baris baru dan karakter carriage return.
Bagaimana cara mengirim nilai yang mengandung tanda ampersand (&) melalui POST?
Gunakan --data-urlencode "field=value with & inside". Dengan -d biasa, tanda ampersand akan dibaca sebagai pemisah bidang dan nilai Anda akan dipotong secara diam-diam pada titik tersebut.
Apakah saya harus mencoba ulang POST yang gagal?
Lakukan dengan hati-hati. POST umumnya tidak idempoten, sehingga pengulangan dapat menghasilkan duplikat — dan timeout tidak menjamin bahwa server tidak memproses permintaan tersebut. Gunakan kunci idempotensi jika API mendukungnya, dan berhati-hatilah dengan --retry-all-errors, terutama melalui proxy di mana kode status 5xx sering kali berarti penolakan, bukan kesalahan sementara.
Kesimpulan
Inti dari topik ini dapat diringkas menjadi satu pertanyaan: jenis konten apa yang diminta oleh endpoint, dan apakah perintah Anda mengirimkannya?
-d mengirimkan data dalam format form-encoded, yang tepat untuk pengiriman formulir tetapi salah untuk JSON — dan ketidaksesuaian tunggal inilah yang menjadi penyebab sebagian besar kesalahan 415 yang ditemui orang. --json adalah opsi yang sebaiknya digunakan, namun kurang dikenal: satu flag yang mengatur penanganan badan pesan dan kedua header, dengan dukungan @ untuk berkas dan stdin.
Selain itu, varian-varian tersebut ada karena alasan spesifik yang patut diingat. --data-raw saat data Anda dimulai dengan @. --data-binary saat baris baru (newline) penting. --data-urlencode saat nilai mengandung karakter yang akan merusak pengkodean formulir. -F untuk pengunggahan berkas sebenarnya, dengan @ untuk melampirkan berkas dan < untuk membaca bidang teks dari berkas tersebut.
Dan jika terjadi kegagalan, jalankan perintah ini dengan -v dan baca baris-baris di > sebelum melakukan perubahan apa pun. Permintaan yang Anda kirimkan seringkali bukanlah permintaan yang Anda kira telah Anda kirimkan, dan kesenjangan itulah yang menjadi sumber kebingungan utama dalam hal ini.
