.\" Automatically generated by Pod::Man 4.11 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "Web::API 3pm" .TH Web::API 3pm "2020-05-03" "perl v5.30.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" Web::API \- A Simple base module to implement almost every RESTful API with just a few lines of configuration .SH "VERSION" .IX Header "VERSION" version 2.7 .SH "SYNOPSIS" .IX Header "SYNOPSIS" \&\fB\s-1NOTE:\s0\fR as of version 2.1 \f(CW\*(C`strict_ssl\*(C'\fR is enabled by default for obvious security reasons, this may break your current library implementation, sorry. .PP Implement the RESTful \s-1API\s0 of your choice in 10 minutes, roughly. .PP .Vb 1 \& package Net::CloudProvider; \& \& use Mouse; \& \& with \*(AqWeb::API\*(Aq; \& \& our $VERSION = "0.1"; \& \& has \*(Aqcommands\*(Aq => ( \& is => \*(Aqrw\*(Aq, \& default => sub { \& { \& list_nodes => { method => \*(AqGET\*(Aq }, \& node_info => { method => \*(AqGET\*(Aq, require_id => 1 }, \& create_node => { \& method => \*(AqPOST\*(Aq, \& default_attributes => { \& allowed_hot_migrate => 1, \& required_virtual_machine_build => 1, \& cpu_shares => 5, \& required_ip_address_assignment => 1, \& primary_network_id => 1, \& required_automatic_backup => 0, \& swap_disk_size => 1, \& }, \& mandatory => [ \& \*(Aqlabel\*(Aq, \& \*(Aqhostname\*(Aq, \& \*(Aqtemplate_id\*(Aq, \& \*(Aqcpus\*(Aq, \& \*(Aqmemory\*(Aq, \& \*(Aqprimary_disk_size\*(Aq, \& \*(Aqrequired_virtual_machine_build\*(Aq, \& \*(Aqcpu_shares\*(Aq, \& \*(Aqprimary_network_id\*(Aq, \& \*(Aqrequired_ip_address_assignment\*(Aq, \& \*(Aqrequired_automatic_backup\*(Aq, \& \*(Aqswap_disk_size\*(Aq, \& ] \& }, \& update_node => { method => \*(AqPUT\*(Aq, require_id => 1 }, \& delete_node => { method => \*(AqDELETE\*(Aq, require_id => 1 }, \& start_node => { \& method => \*(AqPOST\*(Aq, \& require_id => 1, \& post_id_path => \*(Aqstartup\*(Aq, \& }, \& stop_node => { \& method => \*(AqPOST\*(Aq, \& require_id => 1, \& post_id_path => \*(Aqshutdown\*(Aq, \& }, \& suspend_node => { \& method => \*(AqPOST\*(Aq, \& require_id => 1, \& post_id_path => \*(Aqsuspend\*(Aq, \& }, \& }; \& }, \& ); \& \& sub commands { \& my ($self) = @_; \& return $self\->commands; \& } \& \& sub BUILD { \& my ($self) = @_; \& \& $self\->user_agent(_\|_PACKAGE_\|_ . \*(Aq \*(Aq . $VERSION); \& $self\->live_url(\*(Aqhttps://ams01.cloudprovider.net/virtual_machines\*(Aq); \& $self\->content_type(\*(Aqapplication/json\*(Aq); \& $self\->extension(\*(Aqjson\*(Aq); \& $self\->wrapper(\*(Aqvirtual_machine\*(Aq); \& $self\->mapping({ \& os => \*(Aqtemplate_id\*(Aq, \& debian => 1, \& id => \*(Aqlabel\*(Aq, \& disk_size => \*(Aqprimary_disk_size\*(Aq, \& }); \& \& return $self; \& } \& \& 1; .Ve .PP later use as: .PP .Vb 1 \& use Net::CloudProvider; \& \& my $nc = Net::CloudProvider\->new(user => \*(Aqfoobar\*(Aq, api_key => \*(Aqsecret\*(Aq); \& my $response = $nc\->create_node({ \& id => \*(Aqfunnybox\*(Aq, \& hostname => \*(Aqnode.funnybox.com\*(Aq, \& os => \*(Aqdebian\*(Aq, \& cpus => 2, \& memory => 256, \& disk_size => 5, \& allowed_hot_migrate => 1, \& required_virtual_machine_build => 1, \& cpu_shares => 5, \& required_ip_address_assignment => 1, \& }); .Ve .SH "ATTRIBUTES" .IX Header "ATTRIBUTES" .SS "commands" .IX Subsection "commands" most important configuration part of the module which has to be provided by the module you are writing. .PP the following keys are valid/possible: .PP .Vb 10 \& method \& path \& mandatory \& default_attributes \& headers \& extension \& content_type \& incoming_content_type \& outgoing_content_type \& wrapper \& query_keys \& require_id (deprecated, use path) \& pre_id_path (deprecated, use path) \& post_id_path (deprecated, use path) .Ve .PP the request path for commands is being build as: .PP .Vb 1 \& $base_url/$path.$extension .Ve .PP an example for \f(CW\*(C`path\*(C'\fR: .PP .Vb 1 \& path => \*(Aqusers/:user_id/labels\*(Aq .Ve .PP this will add \f(CW\*(C`user_id\*(C'\fR to the list of mandatory keys for this command automatically. .SS "live_url (required)" .IX Subsection "live_url (required)" get/set base \s-1URL\s0 to \s-1API,\s0 can include paths .SS "test_url (optional)" .IX Subsection "test_url (optional)" get/set base \s-1URL\s0 for test system if applicable .SS "test (optional)" .IX Subsection "test (optional)" get/set boolean to run against base \s-1URL\s0 from test system or live system .SS "api_key (required in most cases)" .IX Subsection "api_key (required in most cases)" get/set \s-1API\s0 key (also used as basic auth password) .SS "user (optional)" .IX Subsection "user (optional)" get/set \s-1API\s0 username/account name .SS "api_key_field (optional)" .IX Subsection "api_key_field (optional)" get/set name of the hash key that has to hold the \f(CW\*(C`api_key\*(C'\fR e.g. in \s-1POST\s0 content payloads .SS "api_version (optional)" .IX Subsection "api_version (optional)" get/set \s-1API\s0 version to be used .PP default: 1 .SS "mapping (optional)" .IX Subsection "mapping (optional)" supply mapping table, hashref of format { \*(L"key\*(R" => \*(L"value\*(R", ... } .SS "wrapper (optional)" .IX Subsection "wrapper (optional)" get/set name of the key that is used to wrap all options of a command in. unfortunately some APIs increase the depth of a hash by wrapping everything into a single key (who knows why...), which means this: .PP .Vb 1 \& $wa\->command(%options); .Ve .PP turns \f(CW%options\fR into: .PP .Vb 1 \& { wrapper => \e%options } .Ve .PP before encoding and sending it off. .SS "header (optional)" .IX Subsection "header (optional)" get/set custom headers sent with every request .SS "auth_type" .IX Subsection "auth_type" get/set authentication type. currently supported are only 'basic', 'header', \&'hash_key', 'get_params', 'oauth_header', 'oauth_params' or 'none' .PP default: none .SS "auth_header (optional)" .IX Subsection "auth_header (optional)" get/set the name of the header used for Authorization credentials .PP default: Authorization .SS "auth_header_token_format" .IX Subsection "auth_header_token_format" get/set format of the auth_header token. .PP default: 'Token token=%s' .SS "default_method (optional)" .IX Subsection "default_method (optional)" get/set default \s-1HTTP\s0 method .PP default: \s-1GET\s0 .SS "extension (optional)" .IX Subsection "extension (optional)" get/set file extension, e.g. 'json' .SS "user_agent (optional)" .IX Subsection "user_agent (optional)" get/set User Agent String .PP default: \*(L"Web::API \f(CW$VERSION\fR\*(R" .SS "timeout (optional)" .IX Subsection "timeout (optional)" get/set LWP::UserAgent timeout .SS "strict_ssl (optional)" .IX Subsection "strict_ssl (optional)" enable/disable strict \s-1SSL\s0 certificate hostname checking as a convenience alternatively you can supply your own LWP::Useragent compatible agent for the \f(CW\*(C`agent\*(C'\fR attribute. .PP default: true .SS "agent (optional)" .IX Subsection "agent (optional)" get/set LWP::UserAgent object .SS "retry_http_codes (optional)" .IX Subsection "retry_http_codes (optional)" get/set array of \s-1HTTP\s0 response codes that trigger a retry of the request .SS "retry_errors (optional)" .IX Subsection "retry_errors (optional)" define an array reference of regexes that should trigger a retry of the request if matched against an error found via one of the \f(CW\*(C`error_keys\*(C'\fR .SS "retry_times (optional)" .IX Subsection "retry_times (optional)" get/set number of times a request will be retried at most .PP default: 3 .SS "retry_delay (optional)" .IX Subsection "retry_delay (optional)" get/set delay to wait between retries. accepts float for millisecond support. .PP default: 1.0 .SS "content_type (optional)" .IX Subsection "content_type (optional)" global content type, which is used for in and out going request/response headers and to encode and decode the payload if no other more specific content types are set, e.g. \f(CW\*(C`incoming_content_type\*(C'\fR, \f(CW\*(C`outgoing_content_type\*(C'\fR or content types set individually per command attribute. .PP default: 'text/plain' .SS "incoming_content_type (optional)" .IX Subsection "incoming_content_type (optional)" default: undef .SS "outgoing_content_type (optional)" .IX Subsection "outgoing_content_type (optional)" default: undef .SS "debug (optional)" .IX Subsection "debug (optional)" enable/disabled debug logging .PP default: false .SS "cookies (optional)" .IX Subsection "cookies (optional)" this is used to store and retrieve cookies before and after requests were made to keep authenticated sessions alive for the time this object exists in memory you can add your own cookies to be send with every request. See HTTP::Cookies for more information. .PP default: HTTP::Cookies\->\fBnew()\fR .SS "consumer_secret (required for all oauth_* auth_types)" .IX Subsection "consumer_secret (required for all oauth_* auth_types)" default: undef .SS "access_token (required for all oauth_* auth_types)" .IX Subsection "access_token (required for all oauth_* auth_types)" default: undef .SS "access_secret (required for all oauth_* auth_types)" .IX Subsection "access_secret (required for all oauth_* auth_types)" default: undef .SS "signature_method (required for all oauth_* auth_types)" .IX Subsection "signature_method (required for all oauth_* auth_types)" default: undef .SS "encoder (custom options encoding subroutine)" .IX Subsection "encoder (custom options encoding subroutine)" Receives \f(CW\*(C`\e%options\*(C'\fR and \f(CW\*(C`content\-type\*(C'\fR as the only 2 arguments and has to return a single scalar. .PP default: undef .SS "decoder (custom response content decoding subroutine)" .IX Subsection "decoder (custom response content decoding subroutine)" Receives \f(CW\*(C`content\*(C'\fR and \f(CW\*(C`content\-type\*(C'\fR as the only 2 scalar arguments and has to return a single hash reference. .PP default: undef .SS "oauth_post_body (required for all oauth_* auth_types)" .IX Subsection "oauth_post_body (required for all oauth_* auth_types)" enable/disable adding of command options as extra parameters to the OAuth request generation and therefor be included in the OAuth signature calculation. .PP default: true .SS "error_keys" .IX Subsection "error_keys" get/set list of array keys that will be search for in the decoded response data structure. the same format as for mandatory keys is supported: .PP .Vb 1 \& some.deeply.nested.error.message .Ve .PP will search for an error message at .PP .Vb 1 \& $decoded_response\->{some}\->{deeply}\->{nested}\->{error}\->{messsage} .Ve .PP and if the key exists and its value is defined it will be provided as \&\f(CW\*(C`$response\-\*(C'\fR{error}> and matched against all regexes from the `retry_errors` array ref if provided to trigger a retry on particular errors. .SH "INTERNAL SUBROUTINES/METHODS" .IX Header "INTERNAL SUBROUTINES/METHODS" .SS "nonce" .IX Subsection "nonce" generates new OAuth nonce for every request .SS "log" .IX Subsection "log" .SS "decode" .IX Subsection "decode" .SS "encode" .IX Subsection "encode" .SS "talk" .IX Subsection "talk" .SS "map_options" .IX Subsection "map_options" .SS "check_mandatory" .IX Subsection "check_mandatory" .SS "key_exists" .IX Subsection "key_exists" .SS "wrap" .IX Subsection "wrap" .SS "request" .IX Subsection "request" retry request with delay if \f(CW\*(C`retry_http_codes\*(C'\fR is set, otherwise just try once. .SS "needs_retry" .IX Subsection "needs_retry" returns true if the \s-1HTTP\s0 code or error found match either \f(CW\*(C`retry_http_codes\*(C'\fR or \f(CW\*(C`retry_errors\*(C'\fR respectively. returns false otherwise. .PP if \f(CW\*(C`retry_errors\*(C'\fR are defined it will try to decode the response content and store the decoded structure internally so we don't have to decode again at the end. .PP needs the last response object and the 'Accept' content type header from the request for decoding. .SS "find_error" .IX Subsection "find_error" go through \f(CW\*(C`error_keys\*(C'\fR and find a potential error message in the decoded/parsed response and return it. .SS "format_response" .IX Subsection "format_response" .SS "build_uri" .IX Subsection "build_uri" .SS "build_content_type" .IX Subsection "build_content_type" configure in/out content types .PP order of precedence: 1. per command \f(CW\*(C`incoming_content_type\*(C'\fR / \f(CW\*(C`outgoing_content_type\*(C'\fR 2. per command general \f(CW\*(C`content_type\*(C'\fR 3. content type based on file path extension (only for incoming) 4. global \f(CW\*(C`incoming_content_type\*(C'\fR / \f(CW\*(C`outgoing_content_type\*(C'\fR 5. global general \f(CW\*(C`content_type\*(C'\fR .SS "\s-1DESTROY\s0" .IX Subsection "DESTROY" catch \s-1DESTROY\s0 call and tear down / clean up if necessary at this point there is nothing to do though. This prevents \&\s-1AUTOLOAD\s0 from logging an unknown command error message .SS "\s-1AUTOLOAD\s0 magic" .IX Subsection "AUTOLOAD magic" install a method for each new command and call it in an \f(CW\*(C`eval {}\*(C'\fR to catch exceptions and set an error in a unified way. .SH "BUGS" .IX Header "BUGS" Please report any bugs or feature requests on GitHub's issue tracker . Pull requests welcome. .SH "SUPPORT" .IX Header "SUPPORT" You can find documentation for this module with the \fBperldoc\fR\|(1) command. .PP .Vb 1 \& perldoc Web::API .Ve .PP You can also look for information at: .IP "\(bu" 4 GitHub repository .Sp .IP "\(bu" 4 MetaCPAN .Sp .IP "\(bu" 4 AnnoCPAN: Annotated \s-1CPAN\s0 documentation .Sp .IP "\(bu" 4 \&\s-1CPAN\s0 Ratings .Sp .SH "SEE ALSO" .IX Header "SEE ALSO" HTTP::Cookies, LWP::UserAgent, Net::OAuth .SH "AUTHOR" .IX Header "AUTHOR" Tobias Kirschstein .SH "COPYRIGHT AND LICENSE" .IX Header "COPYRIGHT AND LICENSE" This software is Copyright (c) 2013 by Tobias Kirschstein. .PP This is free software, licensed under: .PP .Vb 1 \& The (three\-clause) BSD License .Ve