Invoke before working with the HTTP layer in Nette – the request, the response, sessions or cookies. Covers Nette\Http\Request (getUrl/UrlScript, getPost, getQuery, getCookie, getFile/getFiles, getHeader, getOrigin, isFrom, isSecured, isAjax, getRemoteAddress and the proxy trap), Nette\Http\Response (setCookie and SameSite, setHeader vs addHeader, redirect, setExpiration, headers-before-output), sessions (Session, SessionSection, set/get/remove, per-key expiration, the `session:` NEON section...
Scanned 9/6/2026
Install to Claude Code
npx -y skills add nette/claude-code --skill nette-http --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Nette Http?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/nette-nette-http)More formats (shields.io, HTML) on the badges page.
---
name: nette-http
description: Invoke before working with the HTTP layer in Nette – the request, the response, sessions or cookies. Covers Nette\Http\Request (getUrl/UrlScript, getPost, getQuery, getCookie, getFile/getFiles, getHeader, getOrigin, isFrom, isSecured, isAjax, getRemoteAddress and the proxy trap), Nette\Http\Response (setCookie and SameSite, setHeader vs addHeader, redirect, setExpiration, headers-before-output), sessions (Session, SessionSection, set/get/remove, per-key expiration, the `session:` NEON section, autoStart), the Sec-Fetch based CSRF / same-origin protection and how to allow a legitimate cross-origin POST, and the SSRF guards UrlValidator and IPAddress. Also trigger for file uploads (FileUpload), Url vs UrlImmutable vs UrlScript, getBasePath/getRelativeUrl, cookie flags, and the `http:` config section. Not for authentication or the `security:` section (see nette-security) nor for building forms (see nette-forms).
---
## Nette Http
Installed with `composer require nette/http`. The framework registers `http.request` (`Nette\Http\Request`), `http.response` (`Nette\Http\Response`), `http.requestFactory` and `session.session` (`Nette\Http\Session`). All autowired – inject `Nette\Http\IRequest` / `Nette\Http\IResponse` / `Nette\Http\Session` by type; in presenters use `$this->getHttpRequest()`, `$this->getHttpResponse()`, `$this->getSession()` / `$this->getSession('sectionName')`.
### Request – read-only, already sanitized
`Nette\Http\Request` is immutable; `RequestFactory::fromGlobals()` strips control characters and invalid UTF-8 from GET/POST/COOKIE and the URL.
```php
$url = $httpRequest->getUrl(); // UrlScript, not a string
$httpRequest->getQuery(); // all GET params; getQuery('id') for one (null if missing)
$httpRequest->getPost('name'); // POST param or null
$httpRequest->getCookie('lang'); // ?string; getCookies() for all
$httpRequest->getHeader('User-Agent'); // ?string, case-insensitive; getHeaders() keys are lowercase
$httpRequest->isSecured(); // HTTPS?
$httpRequest->isAjax(); // X-Requested-With: XMLHttpRequest
$httpRequest->getRemoteAddress(); // ?string
$httpRequest->getBasicCredentials(); // ?array – since 3.2 NOT in $url->getUser()/getPassword()
```
**`getRemoteAddress()` and `isSecured()` are wrong behind a reverse proxy** until you list the proxy, otherwise you get the load balancer's IP and `http` for a TLS-terminated request:
```neon
http:
proxy: 127.0.0.1 # IP, CIDR range, or a list of them
forceHttps: true # 3.3.4+; proxy does not send X-Forwarded-Proto at all
```
`getReferer()` and `getRemoteHost()` are deprecated (`getRemoteHost()` always returns `null`). Use `getOrigin(): ?UrlImmutable` – scheme+host+port from the `Origin` header, absent for plain navigation and same-origin GET.
**File uploads** – always index with `getFile()`, never walk the `getFiles()` tree, because the client controls its shape:
```php
$file = $httpRequest->getFile(['my-form', 'details', 'avatar']); // ?FileUpload
if ($file?->hasFile()) {
$file->getUntrustedName(); // as sent by the user – never use as a path
$file->getSanitizedName(); // safe filename
$file->move($dest);
}
```
`getFile()` returns **null** when the field was submitted empty (`UPLOAD_ERR_NO_FILE`), so always
use `?->`. It is the **form** control that hands you a dummy object instead: `UploadControl`
returns a `FileUpload` carrying that error unless `setNullable()` is on — that is where
`hasFile()` (did the user pick a file?) and `isOk()` (did it arrive intact?) differ.
### Response – mutable, but only before output
Every setter throws `Nette\InvalidStateException` once headers are out; `isSent()` tells you. `setHeader()` replaces (and `null` deletes), `addHeader()` appends another line of the same name.
```php
$httpResponse->setCode(IResponse::S404_NotFound);
$httpResponse->setExpiration('1 hour'); // Cache-Control + Expires; null = no cache at all
$httpResponse->sendAsFile('invoice.pdf');
$httpResponse->redirect($url); // does NOT terminate the script
```
In a presenter use `$this->redirect()` / `$this->getHttpResponse()->redirect(); $this->terminate();` – `Response::redirect()` only sets `Location` and echoes a fallback link.
```php
$httpResponse->setCookie('lang', 'en', '100 days'); // expiration: interval string, date, or null = session cookie
$httpResponse->setCookie('t', 'dark', '1 year', sameSite: SameSite::None, partitioned: true);
$httpResponse->deleteCookie('lang');
```
`SameSite` is now the enum `Nette\Http\SameSite` (`Lax`/`Strict`/`None`); the `IResponse::SameSiteLax` constants are deprecated. Defaults: `sameSite` = `Lax`, `httpOnly` = `true`, `secure` from `http: cookieSecure` (`auto`). `SameSite=None` and `partitioned` force `Secure` on regardless of what you pass. Passing an absolute UNIX timestamp as `$expire` is deprecated – a number means *relative seconds*; a session cookie is `null`, not `0`.
### Sessions
Never call `session_start()`, `$_SESSION` or `session_regenerate_id()` yourself – Nette configures the hardening ini directives (`use_only_cookies`, `use_strict_mode`, `cookie_httponly`, no trans-sid), namespaces data under `$_SESSION['__NF']`, and expires per-section/per-variable metadata at start. A hand-started session bypasses all of it.
The session starts lazily on the first read or write (`autoStart: smart`). Start it explicitly with `$session->start()` if you know you need it and output may already be flowing.
```php
$section = $session->getSection('cart'); // SessionSection; $this->getSession('cart') in a presenter
$section->set('items', $items);
$section->set('flash', $msg, '30 seconds'); // 3rd arg = per-variable expiration
$section->get('items'); // null when missing
$section->remove('items'); // no argument = drop the whole section
$section->setExpiration('20 minutes'); // whole section; ($expire, $variables) for selected keys
$section->removeExpiration('flash');
```
- `set($name, null)` is a **remove**, not a write of `null`.
- `get()` takes **exactly one argument** – `get('key', $default)` throws `ArgumentCountError`. Use `$section->get('key') ?? $default`.
- Magic property access (`$section->items`), `isset()`, `unset()` and `ArrayAccess` are all deprecated. The magic read in particular goes through `&__get()`, so it starts the session **for writing** and materialises the key even on a pure read; `ArrayAccess` reads and `isset()` do not. That is why `set()`/`get()` exist.
- A section's expiration must not exceed the session's own (`session: expiration:`), otherwise you get a warning and the data dies with the session anyway.
```neon
session:
expiration: 14 days # inactivity timeout, default '3 hours'
autoStart: smart # smart (default) | always | never
cookieSamesite: Lax # Lax (default) | Strict | None
name: MYID # any PHP session directive in camelCase, e.g. savePath
handler: @myHandler
debugger: true # session panel in Tracy Bar
```
Login/logout session-id regeneration is handled by nette/security – see **nette-security**.
### CSRF: same-origin via Sec-Fetch, not tokens
**Since nette/http 3.4 the protection is based on the `Sec-Fetch-*` request headers** (Fetch Metadata), which the browser sets itself and page JavaScript can neither forge nor strip. It replaced the older `SameSite=Strict` cookie check (`Request::isSameSite()`, now deprecated).
```php
Request::isFrom(FetchSite|array $site, FetchDest|array|null $dest = null, ?bool $user = null): bool
```
`FetchSite` cases: `SameOrigin`, `SameSite`, `CrossSite`, `None` (direct navigation – typed URL, bookmark, link in an e-mail). `FetchDest` describes the resource kind (`Document`, `Empty`, `Image`, …); `$user = true` requires a genuine user gesture. All given conditions must match.
```php
if (!$httpRequest->isFrom(FetchSite::SameOrigin, FetchDest::Document, user: true)) {
$this->error();
}
```
What this means in practice:
- **POST forms and presenter signals are protected automatically** – `Nette\Forms\Form::receiveHttpData()` and `UI\Form::signalReceived()` require `FetchSite::SameOrigin`, and every `handle*()` method implicitly gets `#[Requires(sameOrigin: true)]`.
- **`$form->addProtection()` is deprecated** (nette/forms 3.3) – the built-in protection is sufficient, and unlike a token it needs no session. Do not add it to new forms.
- To allow a legitimate cross-origin POST: `$form->allowCrossOrigin()` (this replaced `disableSameSiteProtection()`).
- **Behavior change in 3.4:** direct navigation is no longer treated as same-site. A signal reached from a link in an e-mail (`Sec-Fetch-Site: none`) now fails. Mark it explicitly:
```php
#[Requires(sameOrigin: false)]
public function handleUnsubscribe(string $token): void
```
- Safari < 16.4 sends no `Sec-Fetch-*`; Nette falls back to the `SameSite=Strict` cookie `_nss`, sent **only** to such browsers since 3.4. It can only prove "not cross-site", so a check that also demands `$dest` or `$user` returns `false` there. `http: disableNetteCookie: true` switches the cookie off — and with it the only fallback, which breaks every POST from those browsers.
- **Any non-browser client fails the check**, not just old Safari: curl, a mobile app, a webhook or server-to-server traffic sends no `Sec-Fetch-*` and no `_nss`, so `isFrom(SameOrigin)` is false. The failure is quiet — the form reports "not submitted" rather than an error. API endpoints therefore need `#[Requires(sameOrigin: false)]` (or `allowCrossOrigin()` on a form) plus authentication of their own, typically a token.
Form-side details live in **nette-forms**; authentication in **nette-security**.
### SSRF: UrlValidator and IPAddress
New in nette/http 3.4. Validate **every** user-supplied URL before the server fetches it – otherwise an attacker reaches your loopback, private network or cloud metadata at `169.254.169.254`.
```php
use Nette\Http\UrlValidator;
if (!(new UrlValidator)->allows($userUrl)) {
return; // unsafe
}
```
The default policy is deliberately strict: `https` only, port 443 only, and every DNS-resolved A/AAAA address must be publicly routable. Loosen it through constructor named arguments – `schemes`, `ports` (`null` = any), `allowPrivateIps`, `allowLoopback`, `allowLinkLocal`, `allowReserved`, `allowUserinfo`, `hostAllowlist`, `hostBlocklist`. Multicast is rejected unconditionally. Host patterns are exact or `*.example.com` (any subdomain depth, **not** the apex – list both).
- `allowsWithoutDns()` – same checks minus DNS resolution and IP ranges; a cheap pre-filter only.
- `getResolvedIPs()` – returns the validated IPs (`[]` on failure). Pin the fetch to them (`CURLOPT_RESOLVE`) to close the DNS-rebinding race between validation and download.
`Nette\Http\IPAddress` is the value object behind it: `new IPAddress($s)` (throws), `IPAddress::tryFrom()`, `isValid()`, predicates `isPublic()`, `isPrivate()`, `isLoopback()`, `isLinkLocal()`, `isMulticast()`, `isReserved()`, plus `isInRange('10.0.0.0/8')`. IPv4-mapped IPv6 (`::ffff:127.0.0.1`) is normalized, so `isLoopback()` sees through that classic filter bypass.
### Url, UrlImmutable, UrlScript
`Url` is mutable (setters), `UrlImmutable` returns new instances from `with*()` withers, and `UrlScript extends UrlImmutable` adding the notion of where the application root sits. `Request::getUrl()` returns a `UrlScript` with those parts already filled in – don't construct one by hand.
For `http://nette.org/admin/script.php/pathinfo/?name=param#footer` with script path `/admin/script.php`:
| Method | Value |
|---|---|
| `getScriptPath()` | `/admin/script.php` |
| `getBasePath()` | `/admin/` |
| `getBaseUrl()` | `http://nette.org/admin/` |
| `getRelativeUrl()` | `script.php/pathinfo/?name=param#footer` |
| `getPathInfo()` | `/pathinfo/` |
When generating links or asset paths by hand, prefix with `getBasePath()` – a bare `/css/style.css` breaks as soon as the app lives in a subdirectory. `getBasePath()`, `getBaseUrl()` and `getRelativeUrl()` on plain `Url` are deprecated (3.1); they belong to `UrlScript`. `$url->getFragment()` on a request URL is always empty – browsers never send the fragment.
### Online Documentation
For detailed information, use WebFetch on these URLs:
- [HTTP Request](https://doc.nette.org/en/http/request) – Request, RequestFactory, isFrom, uploads, FileUpload
- [HTTP Response](https://doc.nette.org/en/http/response) – Response, headers, cookies
- [Sessions](https://doc.nette.org/en/http/sessions) – Session, SessionSection, expiration
- [SSRF Protection](https://doc.nette.org/en/http/ssrf) – UrlValidator, IPAddress
- [URL Parsing](https://doc.nette.org/en/http/urls) – Url, UrlImmutable, UrlScript
- [HTTP Configuration](https://doc.nette.org/en/http/configuration) – the `http:` and `session:` sections
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!