Python GET/POST request
last modified October 30, 2026 Python 3.15
In this article we show how to send GET and POST requests in Python. In the
examples, we use the standard urllib.request and
socket modules and the urllib3 and
requests libraries. We also show how to process GET and POST
requests in Flask.
HTTP
The Hypertext Transfer Protocol (HTTP) is an application protocol for distributed, collaborative, hypermedia information systems. HTTP is the foundation of data communication for the World Wide Web.
In the examples, we use httpbin.org, which is a freely
available HTTP request and response service; it returns the data of our
request as JSON. We also use example.com, a small HTML page
reserved for documentation examples.
HTTP GET
The HTTP GET method requests a representation of the specified resource.
GET requests:
- should only be used to request a resource
- send parameters in the URL query string
- can be cached
- remain in the browser history
- can be bookmarked
- should never be used when dealing with sensitive data
- have length limits
HTTP POST
The HTTP POST method sends data to the server in the body of the request. It is often used when uploading a file or when submitting a completed web form.
POST requests:
- are used to create a resource or to submit data for processing
- send parameters in the request body, not in the URL
- are not cached by default
- do not remain in the browser history
- cannot be bookmarked
- are suitable for sensitive data when sent over HTTPS
- have no length limits in the protocol
Installing the libraries
The urllib.request and socket modules are part of
the standard library. urllib3 is a powerful HTTP client with
connection pooling and retries. requests is a simple and
elegant HTTP library built on top of urllib3. Flask is a
popular micro web framework based on the Werkzeug WSGI toolkit and the
Jinja template engine.
$ pip install urllib3 requests Flask
GET request with socket
HTTP is a text protocol on top of TCP. To understand what the libraries do for us, we first send a GET request through a plain socket.
import socket
host = 'example.com'
request = (
'GET / HTTP/1.0\r\n'
f'Host: {host}\r\n'
'Accept: text/html\r\n'
'\r\n'
)
with socket.create_connection((host, 80), timeout=10) as s:
s.sendall(request.encode('ascii'))
chunks = []
while data := s.recv(4096):
chunks.append(data)
response = b''.join(chunks).decode()
head, _, body = response.partition('\r\n\r\n')
print(head)
print()
print(body[:60])
We connect to port 80 of the server and send the request line, the
Host header, and an empty line, which ends the headers. Lines
are terminated with \r\n. We use HTTP/1.0, so the server
closes the connection after the response and does not use chunked
encoding. We read the data until recv returns an empty bytes
object and split the response into the head and the body. We print the
status line, the response headers, and the beginning of the HTML document.
$ python socket_get.py HTTP/1.1 200 OK Date: Fri, 30 Oct 2026 20:24:01 GMT Content-Type: text/html; charset=utf-8 Connection: close Server: cloudflare Last-Modified: Thu, 29 Oct 2026 13:15:03 GMT Allow: GET, HEAD Accept-Ranges: bytes Age: 2230 cf-cache-status: HIT CF-RAY: a52d24227804b379-PRG alt-svc: h3=":443"; ma=86400 <!doctype html><html lang=en><head><meta charset=utf-8><link
GET request with urllib.request
The urlopen function from the standard
urllib.request module sends a GET request by default.
from urllib.request import urlopen
with urlopen('https://example.com', timeout=10) as resp:
print(resp.status, resp.reason)
print(resp.headers['Content-Type'])
html = resp.read().decode('utf-8')
start = html.index('<title>') + len('<title>')
end = html.index('</title>')
print(html[start:end])
The returned response object is a context manager. We print the status
code, the reason phrase, and the content type, and then read the body. The
read method returns bytes, which we decode to a string. From
the HTML document, we extract the title.
$ python urllib_get.py 200 OK text/html; charset=utf-8 Example Domain
Query parameters with urllib.request
The parameters of a GET request are sent in the query string of the URL.
The urlencode function builds a properly encoded query string
from a dictionary.
import json
from urllib.parse import urlencode
from urllib.request import urlopen
params = {'name': 'John Doe', 'occupation': 'gardener'}
url = 'https://httpbin.org/get?' + urlencode(params)
print(url)
with urlopen(url, timeout=10) as resp:
data = json.load(resp)
print(data['args'])
print(data['url'])
The space in John Doe is encoded as a plus sign. The
httpbin.org/get endpoint returns the parsed parameters in the
args field. The json.load function parses the
JSON body directly from the response object.
$ python urllib_get_params.py
https://httpbin.org/get?name=John+Doe&occupation=gardener
{'name': 'John Doe', 'occupation': 'gardener'}
https://httpbin.org/get?name=John+Doe&occupation=gardener
POST request with urllib.request
To send a POST request with urllib.request, we create a
Request object with the data.
import json
from urllib.parse import urlencode
from urllib.request import Request, urlopen
form = {'name': 'John Doe', 'occupation': 'gardener'}
body = urlencode(form).encode('ascii')
req = Request('https://httpbin.org/post', data=body, method='POST')
with urlopen(req, timeout=10) as resp:
data = json.load(resp)
print(data['form'])
print(data['headers']['Content-Type'])
The form data is encoded with urlencode and converted to
bytes. When a request has data, urlopen sets the
application/x-www-form-urlencoded content type. The
method parameter makes the method explicit. The
httpbin.org/post endpoint returns the form data in the
form field.
$ python urllib_post.py
{'name': 'John Doe', 'occupation': 'gardener'}
application/x-www-form-urlencoded
GET request with urllib3
The urllib3.request function sends a request using a default
pool manager. For GET requests, the fields parameter is
encoded into the query string.
import urllib3
resp = urllib3.request('GET', 'https://httpbin.org/get',
fields={'name': 'John Doe', 'occupation': 'gardener'})
print(resp.status)
print(resp.headers['Content-Type'])
data = resp.json()
print(data['args'])
print(data['headers']['User-Agent'])
The response object has a json method, which decodes the JSON
body. We print the status code, the content type, the query parameters
received by the server, and the user agent sent by urllib3.
$ python urllib3_get.py
200
application/json
{'name': 'John Doe', 'occupation': 'gardener'}
python-urllib3/2.8.0
POST request with urllib3
A PoolManager reuses connections and lets us set defaults
such as the timeout and the number of retries.
import urllib3
http = urllib3.PoolManager(timeout=10.0, retries=2)
resp = http.request('POST', 'https://httpbin.org/post',
fields={'name': 'John Doe', 'occupation': 'gardener'},
encode_multipart=False)
print(resp.json()['form'])
resp = http.request('POST', 'https://httpbin.org/post',
json={'name': 'John Doe', 'tags': ['garden', 'flowers']})
data = resp.json()
print(data['json'])
print(data['headers']['Content-Type'])
For POST requests, fields are encoded as
multipart/form-data by default; with
encode_multipart=False, they are sent URL-encoded like a web
form. The json parameter serializes a Python object to JSON
and sets the application/json content type.
$ python urllib3_post.py
{'name': 'John Doe', 'occupation': 'gardener'}
{'name': 'John Doe', 'tags': ['garden', 'flowers']}
application/json
GET request with requests
A GET request is generated with the get function. The query
parameters are passed in the params dictionary.
import requests
params = {'name': 'John Doe', 'occupation': 'gardener'}
resp = requests.get('https://httpbin.org/get', params=params, timeout=10)
print(resp.status_code)
print(resp.url)
print(resp.json()['args'])
The url attribute shows the final URL with the encoded query
string. It is a good practice to always set a timeout; by default, requests
waits for the response indefinitely.
$ python requests_get.py
200
https://httpbin.org/get?name=John+Doe&occupation=gardener
{'name': 'John Doe', 'occupation': 'gardener'}
POST request with requests
A POST request is generated with the post function. Form
data is passed in the data parameter, JSON data in the
json parameter.
import requests
url = 'https://httpbin.org/post'
resp = requests.post(url, data={'name': 'Peter'}, timeout=10)
data = resp.json()
print(data['form'])
print(data['headers']['Content-Type'])
resp = requests.post(url, json={'name': 'Peter', 'age': 34}, timeout=10)
data = resp.json()
print(data['json'])
print(data['headers']['Content-Type'])
The first request sends form data, the second one JSON data. The library sets the content type of the request accordingly. httpbin.org returns the JSON keys in alphabetical order.
$ python requests_post.py
{'name': 'Peter'}
application/x-www-form-urlencoded
{'age': 34, 'name': 'Peter'}
application/json
Handling HTTP errors with requests
The requests library does not raise an exception for error status codes.
The raise_for_status method raises an HTTPError
if the status code is 4xx or 5xx.
import requests
for status in (200, 404, 503):
resp = requests.get(f'https://httpbin.org/status/{status}', timeout=10)
try:
resp.raise_for_status()
print(status, 'ok')
except requests.HTTPError as e:
print(e)
The httpbin.org/status/{code} endpoint returns the given
status code. The error message contains the status code, the reason, and
the URL.
$ python requests_errors.py 200 ok 404 Client Error: Not Found for url: https://httpbin.org/status/404 503 Server Error: Service Unavailable for url: https://httpbin.org/status/503
Process GET request in Flask
The following example shows how to process a GET request in a Flask application.
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
return 'Home page'
@app.get('/greet')
def greet():
name = request.args.get('name', 'Guest')
msg = f'Hello {name}'
return msg, 200, {'Content-Type': 'text/plain; charset=utf-8'}
The application creates and sends a message to the client. It uses the
value from the name query parameter, which is available in the
request.args dictionary; if the parameter is missing, the
default value Guest is used. The app.get
decorator maps the greet function to GET requests for the
/greet path. The function returns the body, the status code,
and the headers.
We start the development server in the background and send GET requests
with the curl tool. With the -i option, curl also
prints the response headers. The server logs each request.
$ flask --app flask_get/app run & * Serving Flask app 'flask_get/app' * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Running on http://127.0.0.1:5000 Press CTRL+C to quit $ curl -i 'localhost:5000/greet?name=Lucia' HTTP/1.1 200 OK Server: Werkzeug/3.1.9 Python/3.15.2 Date: Fri, 30 Oct 2026 20:24:09 GMT Content-Type: text/plain; charset=utf-8 Content-Length: 11 Connection: close Hello Lucia 127.0.0.1 - - [30/Oct/2026 21:24:09] "GET /greet?name=Lucia HTTP/1.1" 200 - $ curl localhost:5000/greet Hello Guest 127.0.0.1 - - [30/Oct/2026 21:24:09] "GET /greet HTTP/1.1" 200 -
Process POST request in Flask
The following example shows how to process a POST request in Flask. The application accepts both form and JSON data.
from flask import Flask, request
app = Flask(__name__)
users = []
@app.post('/users')
def create_user():
if request.is_json:
user = request.get_json()
else:
user = request.form.to_dict()
if 'name' not in user:
return {'error': 'name is required'}, 400
users.append(user)
return user, 201
@app.get('/users')
def list_users():
return users
The app.post decorator maps create_user to POST
requests for the /users path. If the request has a JSON
content type, we read the body with get_json; otherwise, we
read the form data from request.form. Without a name, the
function returns an error with the 400 status code. A new user is returned
with the 201 Created status code. Flask converts the returned dictionaries
and lists to JSON responses.
We send form data with the -d option of curl, which makes it a
POST request, and JSON data with the matching content type header. The
third request is rejected because the name is missing. Finally, a GET
request lists the created users.
$ flask --app flask_post/app run &
* Serving Flask app 'flask_post/app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
$ curl -i -d name=Peter -d occupation=driver localhost:5000/users
HTTP/1.1 201 CREATED
Server: Werkzeug/3.1.9 Python/3.15.2
Date: Fri, 30 Oct 2026 20:24:10 GMT
Content-Type: application/json
Content-Length: 39
Connection: close
{"name":"Peter","occupation":"driver"}
127.0.0.1 - - [30/Oct/2026 21:24:10] "POST /users HTTP/1.1" 201 -
$ curl -H 'Content-Type: application/json' -d '{"name": "Lucia", "occupation": "teacher"}' localhost:5000/users
{"name":"Lucia","occupation":"teacher"}
127.0.0.1 - - [30/Oct/2026 21:24:10] "POST /users HTTP/1.1" 201 -
$ curl -d occupation=pilot localhost:5000/users
{"error":"name is required"}
127.0.0.1 - - [30/Oct/2026 21:24:11] "POST /users HTTP/1.1" 400 -
$ curl localhost:5000/users
[{"name":"Peter","occupation":"driver"},{"name":"Lucia","occupation":"teacher"}]
127.0.0.1 - - [30/Oct/2026 21:24:11] "GET /users HTTP/1.1" 200 -
Source
urllib.request - Python documentation
In this article we have generated GET and POST requests in Python and processed them in Flask.
Author
List all Python tutorials.