Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Net Nntp Laravel Package

dariusiii/net_nntp

dariusiii/net_nntp is a Laravel package for connecting to NNTP news servers, offering a simple API to authenticate, list newsgroups, retrieve articles, and integrate Usenet content into PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

dariusiii/net-nntp is a framework-agnostic NNTP (Usenet) client. The 4.1.3 release is a pure performance release — no API changes, no new methods, no deprecations — so anything you had working on 4.1.x keeps working verbatim.

composer require dariusiii/net-nntp

Two entry points, and picking the right one is the first decision you make:

  • DariusIII\NetNntp\Client — the high-level client. Handles parsing of status/header/body responses into PHP structures (array_merge/array_combine-style header assembly, strtotime on Date: headers, ctype_digit on article numbers). Start here.
  • DariusIII\NetNntp\Protocol\Client — the raw protocol layer. Owns the socket, TLS negotiation, and the hot receive loop. Drop to this only when you need commands the high-level client doesn't wrap.

There is no Laravel service provider contract to learn: the client is a plain class, so you wire it yourself (see below). The package.xml PEAR descriptor was removed in 4.1.3 — if any of your tooling or an old pear install script referenced it, that path is gone and composer.json is the only install manifest.

First use case — read the last 20 subject lines of a group:

use DariusIII\NetNntp\Client;

$client = new Client();
$client->connect('news.example.com', 563, true);   // host, port, ssl
$client->authenticate(config('news.user'), config('news.pass'));
$client->selectGroup('comp.lang.php');

foreach ($client->getOverview(0, 20) as $article) {
    echo $article['subject'], PHP_EOL;
}

$client->quit();

Wrap it in a try/finally that always calls quit() — a half-open socket is the most common way to burn through a provider's connection quota.

Implementation Patterns

Bind as a singleton, but reset per job. The connection is stateful: selectGroup() changes the server-side cursor for every subsequent call on that socket. In Laravel, register it in a service provider and forgetInstance() in a queued job's finally — reusing a container-resolved client across jobs means job B inherits job A's selected group.

$this->app->singleton(Client::class, fn () => new Client());

For a multi-tenant or multi-server setup, prefer a small factory that hands out a fresh client per server config rather than one shared singleton.

Separate the fetch and the process. NNTP is a slow, chatty, latency-bound protocol; an article fetch is hundreds of milliseconds. Never do it inline in a request. The natural shape is: a scheduler command dispatches one job per group, the job streams headers, and a second job fetches bodies for the article numbers that passed a filter. That keeps each queue worker's socket short-lived and lets you retry at the message level.

Stream, don't collect. The high-level client returns arrays, which is fine for a header overview of a few hundred rows. For binary or multi-megabyte bodies, iterate and persist each article (disk, S3, a longText column) as it arrives instead of building one giant array — PHP's memory limit is reached far faster than the network is saturated.

Use the protocol client as an escape hatch, not a replacement. For a server-specific extension (e.g. XOVER ranges, XFEATURE COMPRESS GZIP, LIST NEWSGROUPS filters), instantiate DariusIII\NetNntp\Protocol\Client alongside the high-level client and use it only for that command. Mixing them on the same socket is safe as long as you don't interleave a group change.

Retry with backoff, but only on connection errors. Distinguish transport failures (socket refused, TLS handshake, timeout) from protocol rejections (auth failed, no such group, posting not permitted). Retrying an auth failure just gets your IP rate-limited; retrying a timeout on reconnect is exactly right. A retry helper should build a new client each attempt — the package holds no reconnect logic itself.

Test against the protocol boundary. Because Protocol\Client owns the socket, you can swap it for a fake in tests and assert your higher-level service issues the right sequence of selectGroup / getOverview calls, without any live server. Keep one integration test against a real (or locally-run) NNTP daemon for the dot-stuffing and multi-line termination edge cases.

Queue and rate-limit per account. Most providers cap concurrent connections per user. A Redis::throttle('news:account:'.$id)->allow(2)->every(1) around the job body prevents the classic "worked locally, 502'd in production under three workers" failure.

Gotchas and Tips

The 4.1.3 optimisation is invisible to you — and that's the point. Every unqualified built-in call in src/Client.php and src/Protocol/Client.php is now \-prefixed (\strlen, \substr, \trim, \str_starts_with, …). On PHP 8.5 this lets the Zend compiler resolve those calls at compile time and emit single opcodes (ZEND_STRLEN, ZEND_TYPE_CHECK, ZEND_COUNT, ZEND_IN_ARRAY) instead of a full function call, skipping the runtime "does DariusIII\NetNntp\Protocol\trim exist?" namespace fallback check. On older PHP it is harmless but yields nothing. If you vendor, fork, or patch this package, keep the convention — a well-meaning use function strlen; refactor, or a new unqualified in_array() in the hot receive loop, silently gives the win back.

The removed code was the real prize, not the prefixes. 4.1.3 deleted a dead || \strlen($line) < 2 branch in _getTextResponse() (redundant because \str_ends_with($line, "\r\n") already implies length ≥ 2) and a pointless (string) cast around substr($response, 4) in _getStatusResponse(). Both lived in the per-line receive path, so on a large article the saving compounds per iteration. Practical takeaway: when you profile NNTP throughput, profile per-line, not per-call. A 40 KB body is thousands of loop iterations; micro-costs there dominate your wall time, and no amount of queue tuning fixes them.

Connection lifecycle is on you. There's no auto-reconnect and no idle keepalive. Long-lived workers will hit a server-side 400 timeout mid-conversation; treat any false/exception from a fetch as "reconnect and retry once," not "the article doesn't exist."

Group selection is sticky. After selectGroup('alt.test'), a subsequent getArticle(42) refers to article 42 in that group. Article numbers are per-group, not global — caching a fetched article by number alone across groups will hand you the wrong content. Key your cache on group + articleNumber, or on the Message-ID if you have it.

Message-IDs beat numbers. For anything you persist or deduplicate, use the Message-ID header — article numbers are reused and renumbered server-side after retention expiry.

Line endings and dot-stuffing. NNTP terminates lines with CRLF and escapes leading dots. The client normalises this for you on the high-level path; if you hand-craft a command on the protocol layer, send \r\n (never bare \n) and let the client handle the terminating . — appending it yourself produces a "line too long" or a silently desynchronised response stream that surfaces as a hang on the next call.

Timeouts and TLS. Pass SSL explicitly at connect(); there is no autonegotiation. Set a socket timeout via stream_set_timeout (which the package already applies internally on its own read buffer) by configuring your PHP default_socket_timeout, and remember stream_set_read_buffer is invoked in the receive path — very small values will fight it.

Header parsing is best-effort. The high-level client assembles header arrays and runs \strtotime on dates, so malformed Date: fields from a broken poster come back as false. Never assume a parsed date is a DateTime without checking.

Extension points are thin by design. There is no plugin system and no event dispatcher. The supported escape hatch is subclassing or composing the protocol client — that's the intended seam for custom commands, per-command logging, and request/response capture during debugging. If you find yourself needing hooks deep inside _getTextResponse(), fork.

Debugging tip: the fastest way to diagnose a misbehaving server is to log the raw wire bytes at the protocol layer. A 401-vs-480 auth distinction, or the difference between a 220 and an 230, tells you instantly whether you're looking at a credentials problem, a permissions problem, or a parsing problem — and the high-level exception messages flatten that distinction.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource