1
0
Fork
You've already forked Protocol-Tus
0
Perl module for resumable file uploads (tus - https://tus.io/)
  • Perl 100%
2026年05月31日 18:12:15 +02:00
lib/Protocol 0.004 2026年05月31日 18:12:15 +02:00
support Initial import 2026年05月16日 13:28:53 +02:00
t Hopefully fix tests pre 5.38 2026年05月31日 12:13:33 +02:00
xt Initial import 2026年05月16日 13:28:53 +02:00
.gitignore Initial import 2026年05月16日 13:28:53 +02:00
Build.PL First run with Milla 2026年05月22日 22:40:52 +02:00
Changes 0.004 2026年05月31日 18:12:15 +02:00
cpanfile Set up-to-date versions of modules 2026年05月30日 15:42:33 +02:00
cpanfile.snapshot Advance after a few tests 2026年05月20日 23:10:02 +02:00
dist.ini Give up on not generating README.md 2026年05月29日 23:45:13 +02:00
LICENSE First run with Milla 2026年05月22日 22:40:52 +02:00
META.json 0.004 2026年05月31日 18:12:15 +02:00
README.md 0.003 2026年05月30日 15:46:02 +02:00

NAME

Protocol::Tus - Tus protocol handling

VERSION

This document describes Protocol::Tus version {{[ version ]}}.

SYNOPSIS

use Protocol::Tus;
my $tus = Protocol::Tus->new(
 model => {
 class => 'Protocol::Tus::LocalDir',
 args => { root => '/path/to/somewhere' },
 }
);
# assume we have some way of getting requests... This method takes
# care of everything, including X-HTTP-Method-Override
my $request = get_some_input_request();
my $response = $tus->HTTP_request(
 method => $request->{method}, # POST, PATCH, ...
 headers => $request->{headers}, # hash reference
 id => $request->{id}, # id of the upload or undef/''
 body => $request->{body}, # better as a ref to a scalar
 cb_data => $something_for_callback, # anything more you need
);
# The $response is-a Protocol::Tus::Response
# or you can decide to call the relevant methods directly. 
$response = $tus->HTTP_HEAD($request->@{qw< headers id>});
$response = $tus->HTTP_OPTIONS;
$response = $tus->HTTP_PATCH($request->@{qw< headers id body >});
$response = $tus->HTTP_POST($request->@{qw< headers body >}, $cb_data);
$response = $tus->HTTP_DELETE($request->{headers}, $request->{id});
# Again, the $response is-a Protocol::Tus::Response

DESCRIPTION

Implement handling for the Tus protocol in Perl.

Usage Overview

The constructor requires psasing a model, either as an instance or with a specification useful for creating an instance:

my $tus = Protocol::Tus->new(
 model => {
 class => 'Protocol::Tus::LocalDir',
 args => { root => '/path/to/somewhere' },
 },
 generate_location => sub (%args) { ... }, # optional
 on_create => sub (%args) { ... }, # optional
 on_complete => sub (%args) { ... }, # optional
);

The model can be retrieved through the model accessor. It SHOULD be an instance of a class derived from Protocol::Tus::AbstractModel and it sure MUST implement its whole interface.

The main method is HTTP_request, which accepts data gathered from an input request and figures out the best way to address it. This is the suggested way of using the module, because the Tus specification requires honoring the X-HTTP-Method-Override header before any action is done.

Ancillary methods for addressing each specific request method are available too.

Example Usage in Mojolicious

A possible usage in a Mojolicious application might start from defining the base path for the endpoint and how to represent the different uploads; one possible way is in the example below (note: untested):

use v5.24;
use warnings;
use Mojolicious::Lite -signatures;
use Protocol::Tus;
use Ouch qw< bleep >;
# /tus is the endpoint for creating new uploads or getting general
# info about the API, while /tus/:id is to interact with one upload.
# Both are folded onto the same callback that will provide a wrapper
# around Protocol::Tus->HTTP_request.
any '/tus' => \&call_tus;
any '/tus/:id' => \&call_tus;
app->start;
sub call_tus ($c) {
 my $tus = Protocol::Tus->new(
 model => {
 class => 'Protocol::Tus::LocalDir',
 args => { root => '/path/to/storage' },
 },
 );
 my $request = $c->req;
 my $body = $request->body;
 my $tus_response = $tus->HTTP_request(
 $request->method,
 $request->headers->to_hash,
 $c->param('id'), # possibly undefined, it's OK
 \$body,
 cb_data => sub ($id) { $c->url_for("/tus-upload/$id") },
 );
 # log any exception. It's a Ouch object, so you might want to
 # take a look into $exception->data too.
 if (defined(my $exception = $tus_response->exception)) {
 $c->app->log->error(bleep($exception));
 $c->app->log->error($exception->data)
 if eval { $exception->isa('Ouch') };
 }
 # prepare the response to the client
 my $response = $c->res;
 # transfer headers from the $tus_response
 my $res_headers = $c->res->headers;
 my $tus_headers = $tus_response->headers; # hash ref
 $res_headers->header($_ => $tus_headers->{$_})
 for keys($tus_headers->%*);
 # rendering depends on the status code
 my $code = $tus_response->code;
 my $body = $tus_response->body;
 $c->render(status => $code, text => $text);
 return;
}

INTERFACE

The interface is designed to work in a framework-agnostic way, provided that methods are fed with the correct inputs, which in general are:

  • HTTP method

    the HTTP method that was used to send the request. It's a string containing the HTTP method name, case insensitive (it will be turned into a uppercase string).

  • headers

    a reference to a hash holding the request headers. It is assumed that keys are unique in a case-insensitive way, i.e. there are no two keys of interest (as per the Tus specification) that fold onto the same lowercase representation; apart from that, the keys can have whatever case in line with the HTTP specification.

  • id

    the identifier of an upload that the API is supposed to operate on. The Tus specification does not dictate any specific path or query structure and this module follows this pattern, it's up to the caller to figure out the right *upload identifier* for a request, if any.

  • body

    the body of a request, e.g. in a PATCH or POST interaction. As the data might be relevant and best not copied too much around, it's possible and suggested to pass a reference to the scalar value that holds the data.

Each method receives the arguments that it needs either as a key/value pairs list or as a positional argument list, as detailed below. Method starting with HTTP_ return an instance of Protocol::Tus::Response and never raise exceptions.

You are encouraged to determine the structure of the identifier and, in case, make sure that it is consistent with the model adopted. As an example, when using Protocol::Tus::LocalDir, identifiers are generated by the model and are always valid directory names. In any case, it will be up to the caller of the different methods to figure out how to represent these identifiers in the external API and how to extract them from the requests coming from clients.

generate_location

my $coderef = $tus->generate_location;

Accessor to the optional callback to turn an upload identifier into a Location header needed in the 201 response when creating a new upload. If not set, the caller is supposed to generate the Location header independently.

See "new".

new

my $tus = Protocol::Tus->new(
 model => $spec_or_object,
 generate_location => $sub_reference_1,
 on_create => $sub_reference_2,
 on_complete => $sub_reference_3,
);
my $tus = Protocol::Tus->new(
 {
 model => $spec_or_object,
 generate_location => $sub_reference_1,
 on_create => $sub_reference_2,
 on_complete => $sub_reference_3,
 }
);

Create a new instance of a Protocol::Tus.

The following keys are supported:

  • model

    set the model object or the specification to instantiate one. Required parameter.

    If an object is passed, it's assumed to be already valid; otherwise, the $spec_of_object is supposed to be a hash reference with keys class and args to create one.

  • on_complete

    set the callback to invoke when an upload is successfully completed.

    The return value is ignored.

  • on_create

    set the callback to invoke when a new upload is successfully created. If you need to just set the Location header in the response (which you are required to do as per the protocol interface) you might want to look into "generate_location"; this method allows you to do fancier things with a more readable interface.

    The return value is ignored.

  • generate_location

    set the callback to turn an identifier into a Location header when creating a new upload. The signature of this callback is as follows:

     my $location = $callback->(%args);
    

    The return value MUST be a value suitable for a Location header in the response.

The three callbacks are invoked as follows:

$callback->(%args);

The %args supports the following key/value pairs:

model

my $model = $tus->model;

Accessor to the model object. Most probably it will be something derived from Protocol::Tus::AbstractModel, like Protocol::Tus::LocalDir.

on_complete

my $coderef = $tus->on_complete;

Accessor to the optional callback invoked upon completion of an upload.

See "new".

on_create

my $coderef = $tus->on_create;

Accessor to the optional callback invoked upon creation of an upload.

See "new".

HTTP_request

my $response = $tus->HTTP_request(%args);

Handle an incoming request. Not all arguments will be used but they are required to provide a single entry point for the whole Tus interface.

Supported arguments are method, headers, body, id, and cb_data.

HTTP_HEAD

my $response = $tus->HTTP_HEAD($headers, $id);

HTTP_OPTIONS

my $response = $tus->HTTP_OPTIONS;

HTTP_PATCH

my $response = $tus->HTTP_PATCH($headers, $id, $body);

HTTP_POST

my $response = $tus->HTTP_POST($headers, $body, $cb_data);

HTTP_DELETE

my $response = $tus->HTTP_DELETE($headers, $id);

BUGS AND LIMITATIONS

Minimul perl version 5.24.

Report bugs through Codeberg (patches welcome) at https://codeberg.org/polettix/Protocol-Tus.

AUTHOR

Flavio Poletti flavio@polettix.it

COPYRIGHT AND LICENSE

Copyright 2026 by Flavio Poletti flavio@polettix.it

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.