REST

REST

REST is a style of software architecture for distributed hypermedia systems such as the World Wide Web. It has increased in popularity relative to RPC architectures such as SOAP due to the intrinsic de-coupling of client from server that comes from having a uniform interface between heterogeneous systems.
Lawrence  Lesch

Lawrence Lesch

1662385469

Node-wpapi: An Isomorphic JavaScript Client for The WordPress REST API

A WordPress REST API client for JavaScript

This library is an isomorphic client for the WordPress REST API, designed to work with WordPress 5.0 or later. If you are using the older WP REST API plugin or WordPress 4.9, some commands will not work.

About

node-wpapi is an isomorphic JavaScript client for the WordPress REST API that makes it easy for your JavaScript application to request specific resources from a WordPress website. It uses a query builder-style syntax to let you craft the request being made to REST API endpoints, then returns the API's response to your application as a JSON object. And don't let the name fool you: with Webpack or Browserify, node-wpapi works just as well in the browser as it does on the server!

This library is maintained by K. Adam White at Human Made, with contributions from a great community of WordPress and JavaScript developers.

To get started, npm install wpapi or download the browser build and check out "Installation" and "Using the Client" below.

Installation

node-wpapi works both on the server or in the browser. Node.js version 8.6 or higher (or version 8.2.1 with the --harmony flag) is required, and the latest LTS release is recommended.

In the browser node-wpapi officially supports the latest two versions of all evergreen browsers, and Internet Explorer 11.

Install with NPM

To use the library from Node, install it with npm:

npm install --save wpapi

Then, within your application's script files, require the module to gain access to it. As wpapi is both a query builder and a transport layer (i.e. a tool for getting and sending HTTP requests), we leave it up to you as the author of your application whether you need both parts of this functionality. You may use wpapi with superagent if you wish to send and receive HTTP requests using this library, but you may also use only the query builder part of the library if you intend to submit your HTTP requests with fetch, axios or other tools.

To import only the query builder (without the .get(), .create(), .delete(), .update() or .then() chaining methods):

var WPAPI = require( 'wpapi' );

To import the superagent bundle, which contains the full suite of HTTP interaction methods:

var WPAPI = require( 'wpapi/superagent' );

This library is designed to work in the browser as well, via a build system such as Browserify or Webpack; just install the package and require( 'wpapi' ) (or 'wpapi/superagent') from your application code.

Download the UMD Bundle

Alternatively, you may download a ZIP archive of the bundled library code. These files are UMD modules, which may be included directly on a page using a regular <script> tag or required via AMD or CommonJS module systems. In the absence of a module system, the UMD modules will export the browser global variable WPAPI, which can be used in place of require( 'wpapi' ) to access the library from your code.

At present this browser bundle tracks the wpapi/superagent module, and includes Superagent itself.

Upgrading from v1

Require wpapi/superagent

Prior to version 2.0 (currently alpha status) this library shipped with built-in HTTP functionality using Superagent.

If you maintain an existing project which uses this library and wish to upgrade to v2, you may do so by manually installing Superagent:

npm i --save wpapi@alpha superagent

and then changing your require statements to use the wpapi/superagent entrypoint:

--- const WPAPI = require( 'wpapi' );
+++ const WPAPI = require( 'wpapi/superagent' );

Use Promises instead of Callbacks

In version 1, you could use "Node-style" error-first callback functions instead of chaining promises. As of version 2, this callback style is no longer supported; use Promise .then syntax or async/await instead.

// Version 1
wp.posts().get( function( error, posts ) { /* ... */ } );

// Version 2, Promises syntax
wp.posts().get().then( posts => { /* ... */ } );

// Version 2, await syntax
await wp.posts().get();

Using the Client

The module is a constructor, so you can create an instance of the API client bound to the endpoint for your WordPress install:

var WPAPI = require( 'wpapi/superagent' );
var wp = new WPAPI({ endpoint: 'http://src.wordpress-develop.dev/wp-json' });

Once an instance is constructed, you can chain off of it to construct a specific request. (Think of it as a query-builder for WordPress!)

Compatibility Note: As of Version 2.0, Node-style error-first callbacks are no longer supported by this library. All request methods return Promises.

// Request methods return Promises.
wp.posts().get()
    .then(function( data ) {
        // do something with the returned posts
    })
    .catch(function( err ) {
        // handle error
    });

The wp object has endpoint handler methods for every endpoint that ships with the default WordPress REST API plugin.

Once you have used the chaining methods to describe a resource, you may call .create(), .get(), .update() or .delete() to send the API request to create, read, update or delete content within WordPress. These methods are documented in further detail below.

Self-signed (Insecure) HTTPS Certificates

In a case where you would want to connect to a HTTPS WordPress installation that has a self-signed certificate (insecure), you will need to force a connection by placing the following line before you make any wp calls.

process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

Auto-Discovery

It is also possible to leverage the capability discovery features of the API to automatically detect and add setter methods for your custom routes, or routes added by plugins.

To utilize the auto-discovery functionality, call WPAPI.discover() with a URL within a WordPress REST API-enabled site:

var apiPromise = WPAPI.discover( 'http://my-site.com' );

If auto-discovery succeeds this method returns a promise that will be resolved with a WPAPI client instance object configured specifically for your site. You can use that promise as the queue that your client instance is ready, then use the client normally within the .then callback.

Custom Routes will be detected by this process, and registered on the client. To prevent name conflicts, only routes in the wp/v2 namespace will be bound to your instance object itself. The rest can be accessed through the .namespace method on the WPAPI instance, as demonstrated below.

apiPromise.then(function( site ) {
    // If default routes were detected, they are now available
    site.posts().then(function( posts ) {
        console.log( posts );
    }); // etc

    // If custom routes were detected, they can be accessed via .namespace()
    site.namespace( 'myplugin/v1' ).authors()
        .then(function( authors ) { /* ... */ });

    // Namespaces can be saved out to variables:
    var myplugin = site.namespace( 'myplugin/v1' );
    myplugin.authors()
        .id( 7 )
        .then(function( author ) { /* ... */ });
});

Authenticating with Auto-Discovery

While using WPAPI.discover( url ) to generate the handler for your site gets you up and running quickly, it does not provide the same level of customization as instantiating your own new WPAPI object. In order to specify authentication configuration when using autodiscovery, chain a .then onto the initial discovery query to call the .auth method on the returned site object with the relevant credentials (username & password, nonce, etc):

var apiPromise = WPAPI.discover( 'http://my-site.com' ).then(function( site ) {
    return site.auth({
        username: 'admin',
        password: 'always use secure passwords'
    });
});
apiPromise.then(function( site ) {
    // site is now configured to use authentication
})

Cross-Origin Auto-Discovery

When attempting auto-discovery against a remote server in a client-side environment, discovery will fail unless the server is configured for Cross-Origin Resource Sharing (CORS). CORS can be enabled by specifying a set of Access-Control- headers in your PHP code to instruct browsers that requests from remote clients are accepted; these headers also let you control what specific methods and links are exposed to those remote clients.

The WP-REST-Allow-All-Cors plugin will permit CORS requests for all API resources. Auto-discovery will still fail when using this plugin, however, because discovery depends on the presence of a Link header on WordPress pages outside of the root REST API endpoint.

To permit your site to be auto-discovered by client-side REST API clients, add a filter to send_headers to explicitly whitelist the Link header for HEAD requests:

add_action( 'send_headers', function() {
    if ( ! did_action('rest_api_init') && $_SERVER['REQUEST_METHOD'] == 'HEAD' ) {
        header( 'Access-Control-Allow-Origin: *' );
        header( 'Access-Control-Expose-Headers: Link' );
        header( 'Access-Control-Allow-Methods: HEAD' );
    }
} );

Enable CORS at your own discretion. Restricting Access-Control-Allow-Origin to a specific origin domain is often preferable to allowing all origins via *.

Bootstrapping

If you are building an application designed to interface with a specific site, it is possible to sidestep the additional asynchronous HTTP calls that are needed to bootstrap the client through auto-discovery. You can download the root API response, i.e. the JSON response when you hit the root endpoint such as your-site.com/wp-json, and save that JSON file locally; then, in your application code, just require in that JSON file and pass the routes property into the WPAPI constructor or the WPAPI.site method.

Note that you must specify the endpoint URL as normal when using this approach.

var apiRootJSON = require( './my-endpoint-response.json' );
var site = new WPAPI({
    endpoint: 'http://my-site.com/wp-json',
    routes: apiRootJSON.routes
});

// site is now ready to be used with all methods defined in the
// my-endpoint-response.json file, with no need to wait for a Promise.

site.namespace( 'myplugin/v1' ).authors()...

To create a slimmed JSON file dedicated to this particular purpose, see the npm script npm run update-default-routes-json, which will let you download and save an endpoint response to your local project.

In addition to retrieving the specified resource with .get(), you can also .create(), .update() and .delete() resources:

Creating Posts

To create posts, use the .create() method on a query to POST (the HTTP verb for "create") a data object to the server:

// You must authenticate to be able to POST (create) a post
var wp = new WPAPI({
    endpoint: 'http://your-site.com/wp-json',
    // This assumes you are using basic auth, as described further below
    username: 'someusername',
    password: 'password'
});
wp.posts().create({
    // "title" and "content" are the only required properties
    title: 'Your Post Title',
    content: 'Your post content',
    // Post will be created as a draft by default if a specific "status"
    // is not specified
    status: 'publish'
}).then(function( response ) {
    // "response" will hold all properties of your newly-created post,
    // including the unique `id` the post was assigned on creation
    console.log( response.id );
})

This will work in the same manner for resources other than post: you can see the list of required data parameters for each resource on the REST API Developer Handbook.

Updating Posts

To create posts, use the .update() method on a single-item query to PUT (the HTTP verb for "update") a data object to the server:

// You must authenticate to be able to PUT (update) a post
var wp = new WPAPI({
    endpoint: 'http://your-site.com/wp-json',
    // This assumes you are using basic auth, as described further below
    username: 'someusername',
    password: 'password'
});
// .id() must be used to specify the post we are updating
wp.posts().id( 2501 ).update({
    // Update the title
    title: 'A Better Title',
    // Set the post live (assuming it was "draft" before)
    status: 'publish'
}).then(function( response ) {
    console.log( response );
})

This will work in the same manner for resources other than post: you can see the list of required data parameters for each resource in the REST API Developer Handbook.

Requesting Different Resources

A WPAPI instance object provides the following basic request methods:

  • wp.posts()...: Request items from the /posts endpoints
  • wp.pages()...: Start a request for the /pages endpoints
  • wp.types()...: Get Post Type collections and objects from the /types endpoints
  • wp.comments()...: Start a request for the /comments endpoints
  • wp.taxonomies()...: Generate a request against the /taxonomies endpoints
  • wp.tags()...: Get or create tags with the /tags endpoint
  • wp.categories()...: Get or create categories with the /categories endpoint
  • wp.statuses()...: Get resources within the /statuses endpoints
  • wp.users()...: Get resources within the /users endpoints
  • wp.search()...: Find resources of any [REST-enabled] post type matching a ?search= string
  • wp.media()...: Get Media collections and objects from the /media endpoints
  • wp.themes()...: Read information about the active theme from the /themes endpoint (always requires authentication)
  • wp.settings()...: Read or update site settings from the /settings endpoint (always requires authentication)
  • wp.blocks()...: Create queries against the blocks endpoint

All of these methods return a customizable request object. The request object can be further refined with chaining methods, and/or sent to the server via .get(), .create(), .update(), .delete(), .headers(), or .then(). (Not all endpoints support all methods; for example, you cannot POST or PUT records on /types, as these are defined in WordPress plugin or theme code.)

Additional querying methods provided, by endpoint:

  • posts
    • wp.posts(): get a collection of posts (default query)
    • wp.posts().id( n ): get the post with ID n
    • wp.posts().id( n ).revisions(): get a collection of revisions for the post with ID n
    • wp.posts().id( n ).revisions( rn ): get revision rn for the post with ID n
  • pages
    • wp.pages(): get a collection of page items
    • wp.pages().id( n ): get the page with numeric ID n
    • wp.pages().path( 'path/str' ): get the page with the root-relative URL path path/str
    • wp.pages().id( n ).revisions(): get a collection of revisions for the page with ID n
    • wp.pages().id( n ).revisions( rn ): get revision rn for the page with ID n
  • comments
    • wp.comments(): get a collection of all public comments
    • wp.comments().id( n ): get the comment with ID n
  • taxonomies
    • wp.taxonomies(): retrieve all registered taxonomies
    • wp.taxonomies().taxonomy( 'taxonomy_name' ): get a specific taxonomy object with name taxonomy_name
  • categories
    • wp.categories(): retrieve all registered categories
    • wp.categories().id( n ): get a specific category object with id n
  • tags
    • wp.tags(): retrieve all registered tags
    • wp.tags().id( n ): get a specific tag object with id n
  • custom taxonomy terms
  • types
    • wp.types(): get a collection of all registered public post types
    • wp.types().type( 'cpt_name' ): get the object for the custom post type with the name cpt_name
  • statuses
    • wp.statuses(): get a collection of all registered public post statuses (if the query is authenticated—will just display "published" if unauthenticated)
    • wp.statuses().status( 'slug' ): get the object for the status with the slug slug
  • users
    • wp.users(): get a collection of users (will show only users with published content if request is not authenticated)
    • wp.users().id( n ): get the user with ID n (does not require authentication if that user is a published author within the blog)
    • wp.users().me(): get the authenticated user's record
  • media
    • wp.media(): get a collection of media objects (attachments)
    • wp.media().id( n ): get media object with ID n
  • settings
    • wp.settings(): get or update one or many site settings

For security reasons, methods like .revisions() and .settings() require the request to be authenticated, and others such as .users() and .posts() will return only a subset of their information without authentication.

toString()

To get the URI of the resource without making a request, call .toString() at the end of a query chain:

var uriString = wp.posts().id( 7 ).embed().toString();

As the name implies .toString() is not a chaining method, and will return a string containing the full URI; this can then be used with alternative HTTP transports like request, Node's native http, fetch, or jQuery.

API Query Parameters

To set a query parameter on a request, use the .param() method:

// All posts by author w/ ID "7" published before Sept 22, 2016
wp.posts()
  .param( 'before', new Date( '2016-09-22' ) )
  .param( 'author', 7 )...

You can continue to chain properties until you call .then, .get, .create, .update, or .delete on the request chain.

Parameter Shortcut Methods

This library provides convenience methods for many of the most common parameters, like search= (search for a string in post title or content), slug (query for a post by slug), and before and after (find posts in a given date range):

// Find a page with a specific slug
wp.pages().slug( 'about' )...

// Find a post authored by the user with ID #42
wp.posts().author( 42 )...

// Find trashed posts
wp.posts().status( 'trash' )...

// Find posts in status "future" or "draft"
wp.posts().status([ 'draft', 'future' ])...

// Find all categories containing the word "news"
wp.categories().search( 'news' )...

// Find posts from March 2013 (provide a Date object or full ISO-8601 date):
wp.posts().before( '2013-04-01T00:00:00.000Z' ).after( new Date( 'March 01, 2013' ) )...

// Return ONLY sticky posts
wp.posts().sticky( true )...

// Return NO sticky posts
wp.posts().sticky( false )...

// Supply the password for a password-protected post
wp.posts().id( 2501 ).password( 'correct horse battery staple' )...

Paging & Sorting

Convenience methods are also available to set paging & sorting properties like page, per_page (available as .perPage()), offset, order and orderby:

// perPage() sets the maximum number of posts to return. 20 latest posts:
wp.posts().perPage( 20 )...
// 21st through 40th latest posts (*i.e.* the second page of results):
wp.posts().perPage( 20 ).page( 2 )...
// Order posts alphabetically by title:
wp.posts().order( 'asc' ).orderby( 'title' )...

See the section on collection pagination for more information.

Filtering by Taxonomy Terms

A variety of other methods are available to further modify which posts are returned from the API. For example, to restrict the returned posts to only those in category 7, pass that ID to the .categories() method:

wp.posts().categories( 7 )...

Relationships in the REST API are always specified by ID. The slug of a term may change, but the term ID associated with the underlying post will not.

To find the ID of a tag or category for which the slug is known, you can query the associated collection with .slug() and use the ID of the returned object in a two-step process:

wp.categories().slug( 'fiction' )
    .then(function( cats ) {
        // .slug() queries will always return as an array
        var fictionCat = cats[0];
        return wp.posts().categories( fictionCat.id );
    })
    .then(function( postsInFiction ) {
        // These posts are all categorized "fiction":
        console.log( postsInFiction );
    });

To find posts in category 'fiction' and tagged either 'magical-realism' or 'historical', this process can be extended: note that this example uses the RSVP.hash utility for convenience and parallelism, but the same result could easily be accomplished with Promise.all or by chaining each request.

RSVP.hash({
  categories: wp.categories().slug( 'fiction' ),
  tags1: wp.tags().slug('magical-realism'),
  tags2: wp.tags().slug('historical')
}).then(function( results ) {
    // Combine & map .slug() results into arrays of IDs by taxonomy
    var tagIDs = results.tags1.concat( results.tags2 )
        .map(function( tag ) { return tag.id; });
    var categoryIDs = results.categories
        .map(function( cat ) { return cat.id; });
    return wp.posts()
        .tags( tags )
        .categories( categories );
}).then(function( posts ) {
    // These posts are all fiction, either magical realism or historical:
    console.log( posts );
});

This process may seem cumbersome, but it provides a more broadly reliable method of querying than querying by mutable slugs. The first requests may also be avoided entirely by pre-creating and storing a dictionary of term slugs and their associated IDs in your application; however, be aware that this dictionary must be updated whenever slugs change.

It is also possible to add your own slug-oriented query parameters to a site that you control by creating a plugin that registers additional collection parameter arguments.

Excluding terms

Just as .categories() and .tags() can be used to return posts that are associated with one or more taxonomies, two methods exist to exclude posts by their term associations.

  • .excludeCategories() is a shortcut for .param( 'categories_exclude', ... ) which excludes results associated with the provided category term IDs
  • .excludeTags() is a shortcut for .param( 'tags_exclude', ... ) which excludes results associated with the provided tag term IDs

Custom Taxonomies

Just as the ?categories and ?categories_exclude parameters are available for use with the built-in taxonomies, any custom taxonomy that is registered with a rest_base argument has a ?{taxonomy rest_base} and ?{taxonomy rest_base}_exclude parameter available, which can be set directly using .param. For the custom taxonomy genres, for example:

  • wp.posts().param( 'genres', [ array of genre term IDs ]): return only records associated with any of the provided genres
  • wp.posts().param( 'genres_exclude', [ array of genre term IDs ]): return only records associated with none of the provided genres

Retrieving posts by author

The .author() method also exists to query for posts authored by a specific user (specified by ID).

// equivalent to .param( 'author', 42 ):
wp.posts().author( 42 ).get();

// last value wins: this queries for author == 71
wp.posts().author( 42 ).author( 71 ).get();

As with categories and tags, the /users endpoint may be queried by slug to retrieve the ID to use in this query, if needed.

Password-Protected posts

The .password() method (not to be confused with the password property of .auth()!) sets the password to use to view a password-protected post. Any post for which the content is protected will have protected: true set on its content and excerpt properties; content.rendered and excerpt.rendered will both be '' until the password is provided by query string.

wp.posts().id( idOfProtectedPost )
    .then(function( result ) {
        console.log( result.content.protected ); // true
        console.log( result.content.rendered ); // ""
    });

wp.posts.id( idOfProtectedPost )
    // Provide the password string with the request
    .password( 'thepasswordstring' )
    .then(function( result ) {
        console.log( result.content.rendered ); // "The post content"
    });

Other Filters

The ?filter query parameter is not natively supported within the WordPress core REST API endpoints, but can be added to your site using the rest-filter plugin. filter is a special query parameter that lets you directly specify many WP_Query arguments, including tag, author_name, and other public query vars. Even more parameters are available for use with filter once you authenticate with the API.

If your environment supports this parameter, other filtering methods will be available if you initialize your site using auto-discovery, which will auto-detect the availability of filter:

WPAPI.discover( 'http://mysite.com' )
    .then(function( site ) {
        // Apply an arbitrary `filter` query parameter:
        // All posts belonging to author with nicename "jadenbeirne"
        wp.posts().filter( 'author_name', 'jadenbeirne' ).get();

        // Query by the slug of a category or tag
        // Get all posts in category "islands" and tags "clouds" & "sunset"
        // (filter can either accept two parameters, as above where it's called with
        // a key and a value, or an object of parameter keys and values, as below)
        wp.posts().filter({
            category_name: 'islands',
            tag: [ 'clouds', 'sunset' ]
        })...

        // Query for a page at a specific URL path
        wp.pages().filter( 'pagename', 'some/url/path' )..
    });

Date Filter Methods

?before and ?after provide first-party support for querying by date, but should you have access to filter then three additional date query methods are available to return posts from a specific month, day or year:

  • .year( year ): find items published in the specified year
  • .month( month ): find items published in the specified month, designated by the month index (1–12) or name (e.g. "February")
  • .day( day ): find items published on the specified day

Uploading Media

Files may be uploaded to the WordPress media library by creating a media record using the .media() collection handler.

The file to upload can be specified as

  • a String describing an image file path, e.g. '/path/to/the/image.jpg'
  • a Buffer with file content, e.g. Buffer.from() (or the result of a readFile call)
  • a file object from a <input> element, e.g. document.getElementById( 'file-input' ).files[0]

The file is passed into the .file() method:

wp.media().file(content [, name])...

The optional second string argument specifies the file name to use for the uploaded media. If the name argument is omitted file() will try to infer a filename from the provided file path. Note that when uploading a Buffer object name is a required argument, because no name can be automatically inferred from the buffer.

Adding Media to a Post

If you wish to associate a newly-uploaded media record to a specific post, you must use two calls: one to first upload the file, then another to associate it with a post. Example code:

wp.media()
    // Specify a path to the file you want to upload, or a Buffer
    .file( '/path/to/the/image.jpg' )
    .create({
        title: 'My awesome image',
        alt_text: 'an image of something awesome',
        caption: 'This is the caption text',
        description: 'More explanatory information'
    })
    .then(function( response ) {
        // Your media is now uploaded: let's associate it with a post
        var newImageId = response.id;
        return wp.media().id( newImageId ).update({
            post: associatedPostId
        });
    })
    .then(function( response ) {
        console.log( 'Media ID #' + response.id );
        console.log( 'is now associated with Post ID #' + response.post );
    });

If you are uploading media from the client side, you can pass a reference to a file input's file list entry in place of the file path:

wp.media()
    .file( document.getElementById( 'file-input' ).files[0] )
    .create()...

Custom Routes

Support for Custom Post Types is provided via the .registerRoute method. This method returns a handler function which can be assigned to your site instance as a method, and takes the same namespace and route string arguments as rest_register_route:

var site = new WPAPI({ endpoint: 'http://www.yoursite.com/wp-json' });
site.myCustomResource = site.registerRoute( 'myplugin/v1', '/author/(?P<id>)' );
site.myCustomResource().id( 17 ); // => myplugin/v1/author/17

The string (?P<id>) indicates that a level of the route for this resource is a dynamic property named ID. By default, properties identified in this fashion will not have any inherent validation. This is designed to give developers the flexibility to pass in anything, with the caveat that only valid IDs will be accepted on the WordPress end.

You might notice that in the example from the official WP-API documentation, a pattern is specified with a different format: this is a regular expression designed to validate the values that may be used for this capture group.

var site = new WPAPI({ endpoint: 'http://www.yoursite.com/wp-json' });
site.myCustomResource = site.registerRoute( 'myplugin/v1', '/author/(?P<id>\\d+)' );
site.myCustomResource().id( 7 ); // => myplugin/v1/author/7
site.myCustomResource().id( 'foo' ); // => Error: Invalid path component: foo does not match (?P<a>\d+)

Adding the regular expression pattern (as a string) enabled validation for this component. In this case, the \\d+ will cause only numeric values to be accepted.

NOTE THE DOUBLE-SLASHES in the route definition here, however:

'/author/(?P<id>\\d+)'

This is a JavaScript string, where \ must be written as \\ to be parsed properly. A single backslash will break the route's validation.

Each named group in the route will be converted into a named setter method on the route handler, as in .id() in the example above: that name is taken from the <id> in the route string.

The route string 'pages/(?P<parentPage>[\\d]+)/revisions/(?P<id>[\\d]+)' would create the setters .parentPage() and id(), permitting any permutation of the provided URL to be created.

Setter method naming for named route components

In the example above, registering the route string '/author/(?P<id>\\d+)' results in the creation of an .id() method on the resulting resource handler:

site.myCustomResource().id( 7 ); // => myplugin/v1/author/7

If a named route component (e.g. the "id" part in (?P<id>\\d+), above) is in snake_case, then that setter will be converted to camelCase instead, as with some_part below:

site.myCustomResource = site.registerRoute( 'myplugin/v1', '/resource/(?P<some_part>\\d+)' );
site.myCustomResource().somePart( 7 ); // => myplugin/v1/resource/7

Non-snake_cased route parameter names will be unaffected.

Query Parameters & Filtering Custom Routes

Many of the filtering methods available on the built-in collections are built in to custom-registered handlers, including .page(), .perPage(), .search(), .include()/.exclude() and .slug(); these parameters are supported across nearly all API endpoints, so they are made available automatically to custom endpoints as well.

However not every filtering method is available by default, so for convenience a configuration object may be passed to the registerRoute method with a params property specifying additional query parameters to support. This makes it very easy to add existing methods like .before() or .after() to your own endpoints:

site.handler = site.registerRoute( 'myplugin/v1', 'collection/(?P<id>)', {
    // Listing any of these parameters will assign the built-in
    // chaining method that handles the parameter:
    params: [ 'before', 'after', 'author', 'parent', 'post' ]
});
// yields
site.handler().post( 8 ).author( 92 ).before( dateObj )...

If you wish to set custom parameters, for example to query by the custom taxonomy genre, you can use the .param() method as usual:

site.handler().param( 'genre', genreTermId );

but you can also specify additional query parameter names and a .param() wrapper function will be added automatically. e.g. here .genre( x ) will be created as a shortcut for .param( 'genre', x ):

site.books = site.registerRoute( 'myplugin/v1', 'books/(?P<id>)', {
    params: [ 'genre' ]
});
// yields
site.books().genre([ genreId1, genreId2 ])...

Mixins

To assign completely arbitrary custom methods for use with your custom endpoints, a configuration object may be passed to the registerRoute method with a mixins property defining any functions to add:

site.handler = site.registerRoute( 'myplugin/v1', 'collection/(?P<id>)', {
    mixins: {
        myParam: function( val ) {
            return this.param( 'my_param', val );
        }
    }
});

This permits a developer to extend an endpoint with arbitrary parameters in the same manner as is done for the automatically-generated built-in route handlers.

Note that mixins should always return this to support method chaining.

Embedding Data

Data types in WordPress are interrelated: A post has an author, some number of tags, some number of categories, etc. By default, the API responses will provide pointers to these related objects, but will not embed the full resources: so, for example, the "author" property would come back as just the author's ID, e.g. "author": 4.

This functionality provides API consumers the flexibility to determine when and how they retrieve the related data. However, there are also times where an API consumer will want to get the most data in the fewest number of responses. Certain resources (author, comments, tags, and categories, to name a few) support embedding, meaning that they can be included in the response if the _embed query parameter is set.

To request that the API respond with embedded data, simply call .embed() as part of the request chain:

wp.posts().id( 2501 ).embed()...

This will include an ._embedded object in the response JSON, which contains all of those embeddable objects:

{
    "_embedded": {
        "author": [ /* ... */ ],
        "replies": [ /* ... */ ],
        "wp:attachment": [ /* ... */ ],
        "wp:term": [
            [ {}, {} /* category terms */ ],
            [ {} /* tag terms */ ],
            /* etc... */
        ],
        "wp:meta": [ /* ... */ ]
    }
}

For more on working with embedded data, check out the WP-API documentation.

Collection Pagination

WordPress sites can have a lot of content—far more than you'd want to pull down in a single request. The API endpoints default to providing a limited number of items per request, the same way that a WordPress site will default to 10 posts per page in archive views. The number of objects you can get back can be adjusted by calling the perPage method, but perPage is capped at 100 items per request for performance reasons. To work around these restrictions, the API provides headers so the API will frequently have to return your posts be unable to fit all of your posts in a single query.

Using Pagination Headers

Paginated collection responses are augmented with a _paging property derived from the collection's pagination headers. That _paging property on the response object contains some useful metadata:

  • .total: The total number of records matching the provided query
  • .totalPages: The number of pages available (total / perPage)
  • .next: A WPRequest object pre-bound to the next page of results
  • .prev: A WPRequest object pre-bound to the previous page of results
  • .links: an object containing the parsed link HTTP header data (when present)

The existence of the _paging.links.prev and _paging.links.next properties can be used as flags to conditionally show or hide your paging UI, if necessary, as they will only be present when an adjacent page of results is available.

You can use the next and prev properties to traverse an entire collection, should you so choose. For example, this snippet will recursively request the next page of posts and concatenate it with existing results, in order to build up an array of every post on your site:

var _ = require( 'lodash' );
function getAll( request ) {
  return request.then(function( response ) {
    if ( ! response._paging || ! response._paging.next ) {
      return response;
    }
    // Request the next page and return both responses as one collection
    return Promise.all([
      response,
      getAll( response._paging.next )
    ]).then(function( responses ) {
      return _.flatten( responses );
    });
  });
}
// Kick off the request
getAll( wp.posts() ).then(function( allPosts ) { /* ... */ });

Be aware that this sort of unbounded recursion can take a very long time: if you use this technique in your application, we strongly recommend caching the response objects in a local database rather than re-requesting from the WP remote every time you need them.

Depending on the amount of content in your site loading all posts into memory may also exceed Node's available memory, causing an exception. If this occurs, try to work with smaller subsets of your data at a time.

Requesting a Specific Page

You can also use a .page(pagenumber) method on calls that support pagination to directly get that page. For example, to set the API to return 5 posts on every page of results, and to get the third page of results (posts 11 through 15), you would write

wp.posts().perPage( 5 ).page( 3 ).then(/* ... */);

Using offset

If you prefer to think about your collections in terms of offset, or how many items "into" the collection you want to query, you can use the offset parameter (and parameter convenience method) instead of page. These are equivalent:

// With .page()
wp.posts().perPage( 5 ).page( 3 )...
// With .offset()
wp.posts().perPage( 5 ).offset( 10 )...

Customizing HTTP Request Behavior

By default node-wpapi uses the superagent library internally to make HTTP requests against the API endpoints. Superagent is a flexible tool that works on both the client and the browser, but you may want to use a different HTTP library, or to get data from a cache when available instead of making an HTTP request. To facilitate this, node-wpapi lets you supply a transport object when instantiating a site client to specify custom functions to use for one (or all) of GET, POST, PUT, DELETE & HEAD requests.

This is advanced behavior; you will only need to utilize this functionality if your application has very specific HTTP handling or caching requirements.

In order to maintain consistency with the rest of the API, custom transport methods should take in a WordPress API route handler query object (e.g. the result of calling wp.posts()... or any of the other chaining resource handlers)and a data object (for POST, PUT and DELETE requests).

Note: Node-style error-first callbacks are no longer supported by this library as of version 2.0. Custom transport methods should therefore not accept or expect a third optional callback parameter.

The default HTTP transport methods are available as WPAPI.transport (a property of the constructor object) and may be called within your transports if you wish to extend the existing behavior, as in the example below.

Example: Cache requests in a simple dictionary object, keyed by request URI. If a request's response is already available, serve from the cache; if not, use the default GET transport method to retrieve the data, save it in the cache, and return it to the consumer:

var site = new WPAPI({
  endpoint: 'http://my-site.com/wp-json',
  transport: {
    // Only override the transport for the GET method, in this example
    // Transport methods should take a wpreq object:
    get: function( wpreq ) {
      var result = cache[ wpreq ];
      // If a cache hit is found, return it wrapped in a Promise:
      if ( result ) {
        // Return the data as a promise
        return Promise.resolve( result );
      }

      // Delegate to default transport if no cached data was found
      return WPAPI.transport.get( wpreq, cb ).then(function( result ) {
        cache[ wpreq ] = result;
        return result;
      });
    }
  }
});

You may set one or many custom HTTP transport methods on an existing WP site client instance (for example one returned through auto-discovery by calling the .transport() method on the site client instance and passing an object of handler functions:

site.transport({
    get: function( wpreq ) { /* ... */},
    put: function( wpreq, data ) { /* ... */}
});

Note that these transport methods are the internal methods used by create and .update, so the names of these methods therefore map to the HTTP verbs "get", "post", "put", "head" and "delete"; name your transport methods accordingly or they will not be used.

Specifying HTTP Headers

If you need to send additional HTTP headers along with your request (for example to provide a specific Authorization header for use with alternative authentication schemes), you can use the .setHeaders() method to specify one or more headers to send with the dispatched request:

Set headers for a single request

// Specify a single header to send with the outgoing request
wp.posts().setHeaders( 'Authorization', 'Bearer xxxxx.yyyyy.zzzzz' )...

// Specify multiple headers to send with the outgoing request
wp.posts().setHeaders({
    Authorization: 'Bearer xxxxx.yyyyy.zzzzz',
    'Accept-Language': 'pt-BR'
})...

Set headers globally

You can also set headers globally on the WPAPI instance itself, which will then be used for all subsequent requests created from that site instance:

// Specify a header to be used by all subsequent requests
wp.setHeaders( 'Authorization', 'Bearer xxxxx.yyyyy.zzzzz' );

// These will now be sent with an Authorization header
wp.users().me()...
wp.posts().id( unpublishedPostId )...

Authentication

You must be authenticated with WordPress to create, edit or delete resources via the API. Some WP-API endpoints additionally require authentication for GET requests in cases where the data being requested could be considered private: examples include any of the /users endpoints, requests where the context query parameter is true, and /revisions for posts and pages, among others.

Basic Authentication

This library currently supports basic HTTP authentication. To authenticate with your WordPress install,

  1. Download and install the Basic Authentication handler plugin on your target WordPress site. (Note that the basic auth handler is not curently available through the plugin repository: you must install it manually.)
  2. Activate the plugin.
  3. Specify the username and password of an authorized user (a user that can edit_posts) when instantiating the WPAPI request object:
var wp = new WPAPI({
    endpoint: 'http://www.website.com/wp-json',
    username: 'someusername',
    password: 'thepasswordforthatuser'
});

Now any requests generated from this WPAPI instance will use that username and password for basic authentication if the targeted endpoint requires it.

As an example, wp.users().me() will automatically enable authentication to permit access to the /users/me endpoint. (If a username and password had not been provided, a 401 error would have been returned.)

Manually forcing authentication

Because authentication may not always be set when needed, an .auth() method is provided which can enable authentication for any request chain:

// This will authenticate the GET to /posts/id/817
wp.posts().id( 817 ).auth().get(...

This .auth method can also be used to manually specify a username and a password as part of a request chain:

// Use username "mcurie" and password "nobel" for this request
wp.posts().id( 817 ).auth( {username: 'mcurie', password: 'nobel'} ).get(...

This will override any previously-set username or password values.

Authenticate all requests for a WPAPI instance

It is possible to make all requests from a WPAPI instance use authentication by setting the auth option to true on instantiation:

var wp = new WPAPI({
    endpoint: // ...
    username: // ...
    password: // ...
    auth: true
});

SECURITY WARNING

Please be aware that basic authentication sends your username and password over the wire, in plain text. We only recommend using basic authentication in production if you are securing your requests with SSL.

More robust authentication methods will hopefully be added; we would welcome contributions in this area!

Cookie Authentication

When the library is loaded from the frontend of the WordPress site you are querying against, you may authenticate your REST API requests using the built in WordPress Cookie authentication by creating and passing a Nonce with your API requests.

First localize your scripts with an object with root-url and nonce in your theme's functions.php or your plugin:

function my_enqueue_scripts() {
    wp_enqueue_script( 'app', get_template_directory_uri() . '/assets/dist/bundle.js', array(), false, true );
    wp_localize_script( 'app', 'WP_API_Settings', array(
        'endpoint' => esc_url_raw( rest_url() ),
        'nonce' => wp_create_nonce( 'wp_rest' )
    ) );
}
add_action( 'wp_enqueue_scripts', 'my_enqueue_scripts' );

And then use this nonce when initializing the library:

var WPAPI = require( 'wpapi/superagent' );
var wp = new WPAPI({
    endpoint: window.WP_API_Settings.endpoint,
    nonce: window.WP_API_Settings.nonce
});

API Documentation

In addition to the above getting-started guide, we have automatically-generated API documentation.

Issues

If you identify any errors in this module, or have an idea for an improvement, please open an issue. We're excited to see what the community thinks of this project, and we would love your input!

Contributing

We welcome contributions large and small. See our contributor guide for more information.

Download Details:

Author: WP-API
Source Code: https://github.com/WP-API/node-wpapi 
License: MIT license

#javascript #node #wordpress #rest 

Node-wpapi: An Isomorphic JavaScript Client for The WordPress REST API

Tentacool: REST API to Manage Linux Networking Via Netlink

tentacool  

Description

tentacool is a Go server controlled via RESTful API through a Unix Domain Socket.

Goal

Main goal is to manage all under the hood services for a simple "box". All done with a auditable, fast and bulletproof software.

So many software do frontend, backend and system... And finally run in root by easiness.

To build

Be sure to set the correct GOPATH and GOROOT environment variables. You can make use of godeb which set you up with the version of Go you want. (Tentacool is using >= 1.2)

Build Tentacool using gb.

An automatic of the executable from master branch can also be found on drone.io.

How-to GB

# Get GB
go get github.com/constabulary/gb/...
# Fetch dependencies
gb vendor restore
# Build tentacool
gb build
./bin/tentacool -help

Configuration

Recommended /etc/network/interfaces config for your default interface (for instance eth0):

auto eth0
iface eth0 inet manual
  pre-up ifconfig $IFACE up
  post-down ifconfig $IFACE down

API

addresses

address object

  • link: interface to manage
  • ip: ip to add (CIDR format)
  • id

GET /addresses

List all current addresses

Response

GET /addresses/:id

Response

POST /addresses

Add a new address to manage.

parameters

Response

  • address
  • headers
    • X-Error: if address is stored in BD but fail to by apply.

Example

  • without id
==>
{
  "link":"eth0",
  "ip":"192.168.32.11/32",
}
<==
{
  "id":"1",
  "link":"eth0",
  "ip":"192.168.32.11/32",
}
  • with id
==>
{
  "id":"foo",
  "link":"eth0",
  "ip":"192.168.32.12/32",
}
<==
{
  "id":"foo",
  "link":"eth0",
  "ip":"192.168.32.12/32",
}

PUT /addresses/:id

Modify an existing address

parameters

Response

  • address
  • headers
    • X-Error: if address is stored in BD but fail to by apply.

dhcp

GET /dhcp

Checks if DHCP is running on the default interface.

Response

{'active': true|false}

POST /dhcp

Activate/deactive DHCP for default interface.

parameters

  • active true or false

Download Details:

Author: Surycat
Source Code: https://github.com/surycat/tentacool 

#go #golang #rest #api 

Tentacool: REST API to Manage Linux Networking Via Netlink
Hunter  Krajcik

Hunter Krajcik

1661203440

Rest_client: A Dart Implementation Of The A REST Client That Supports

rest_client

A Dart and Flutter compatible library to simplify creating REST based API calls.

For Flutter based applications, this will offload the JSON decoding to a separate Isolate to avoid janking the UI thread on large JSON responses. For Dart Web / AngularDart based applications, this processes the JSON on the main thread because Isolates are not supported.

Using the library

Add the repo to your Flutter pubspec.yaml file.

dependencies:
  rest_client: <<version>> 

Then run...

flutter packages get

Authorizers

The API Client offers the following two built in authorizers.

  • BasicAuthorizer -- To authenticate against an API using the BASIC username / password security models.
  • TokenAuthorizer -- To authorize against an API using Bearer token based authorization.

Example

import 'package:rest_client/rest_client.dart' as rc;

...

var client = rc.Client();

var request = rc.Request(
  url: 'https://google.com',
);

var response = client.execute(
  authorizor: rc.TokenAuthorizer(token: 'my_token_goes_here'),
  request: request, 
);

var body = response.body;
// do further processing here...

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add rest_client

With Flutter:

 $ flutter pub add rest_client

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  rest_client: ^2.1.4+8

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:rest_client/rest_client.dart';

 

Download Details:

Author: Peiffer-innovations
Source Code: https://github.com/peiffer-innovations/rest_client 
License: MIT license

#flutter #dart #rest #client 

Rest_client: A Dart Implementation Of The A REST Client That Supports

A Client for The Atlassian REST APIs in Dart

A client for the Atlassian REST APIs in Dart

This is a Dart client for the APIs of:

All the client are generated from the OpenAPI definition files provided by Atlassian See Atlassian documentation of the APIs

Usage

Authentication

import 'dart:io';
import 'package:atlassian_apis/jira_platform.dart';

void main() async {
  // This example uses API Token authentication.
  // Alternatively, you can use OAuth.
  var user = Platform.environment['ATLASSIAN_USER']!;
  var apiToken = Platform.environment['ATLASSIAN_API_TOKEN']!;

  // Create an authenticated http client.
  var client = ApiClient.basicAuthentication(
      Uri.https('<your-account>.atlassian.net', ''),
      user: user,
      apiToken: apiToken);

  // Create the API wrapper from the http client
  var jira = JiraPlatformApi(client);

  // Communicate with the APIs..
  await jira.projects.searchProjects();

  // Close the client to quickly terminate the process
  client.close();
}

Jira

import 'dart:convert';
import 'package:atlassian_apis/jira_platform.dart';

void main() async {
  // Create Jira API from an authenticated client
  var jira = JiraPlatformApi(apiClient);

  // Create a comment
  var info = await jira.issueComments
      .addComment(issueIdOrKey: 'BR-123', body: Comment(body: 'A comment'));
  printJson(info);
}

Service desk

import 'dart:convert';
import 'dart:io';
import 'package:atlassian_apis/service_management.dart';
import 'package:path/path.dart' as path;

void main() async {
  // Create the API for Service Desk
  var serviceManagement = ServiceManagementApi(apiClient);

  // Fetch some data
  var desks = await serviceManagement.servicedesk.getServiceDesks();
  printJson(desks);

  // Create a new customer
  var newCustomer = await serviceManagement.customer
      .createCustomer(body: CustomerCreateDTO(email: '..', displayName: '...'));
  printJson(newCustomer);

  // Upload a file
  var file = File('some_file.png');
  var attachment = await serviceManagement.servicedesk.attachTemporaryFile(
      serviceDeskId: '1',
      file: MultipartFile('file', file.openRead(), file.lengthSync(),
          filename: path.basename(file.path)));

  // Create a new customer request with attachment
  await serviceManagement.request.createCustomerRequest(
      body: RequestCreateDTO(requestFieldValues: {
    'summary': 'The issue summary',
    'attachment': attachment.temporaryAttachments
        .map((e) => e.temporaryAttachmentId)
        .toList()
  }));
}

Confluence

import 'dart:convert';
import 'dart:io';
import 'package:atlassian_apis/confluence.dart';
import 'package:http/http.dart';

void main() async {
  // Create Confluence API from an authenticated client
  var confluence = ConfluenceApi(apiClient);

  // Get a page
  var page =
      await confluence.content.getContentById(id: '1234', expand: ['version']);

  // Update a page
  await confluence.content.updateContent(
    id: page.id!,
    body: ContentUpdate(
      version: ContentUpdateVersion(number: page.version!.number + 1),
      title: page.title!,
      type: 'page',
      body: ContentUpdateBody(
        storage: ContentBodyCreateStorage(
          value: 'new content',
          representation: ContentBodyCreateStorageRepresentation.storage,
        ),
      ),
    ),
  );

  // Get page attachments
  var attachments = await confluence.contentChildrenAndDescendants
      .getContentChildrenByType(id: '1234', type: 'attachment');
  var existingAttachments = attachments.results.toList();

  // Delete attachments
  await confluence.content.deleteContent(id: existingAttachments.first.id!);

  // Create new attachments
  var file = File('my_file.pdf');
  await confluence.contentAttachments.createOrUpdateAttachments(
      id: '1234',
      body: MultipartFile('file', file.openRead(), file.lengthSync(),
          filename: 'file.pdf'));

  // Close the http client
  confluence.close();
}

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add atlassian_apis

With Flutter:

 $ flutter pub add atlassian_apis

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  atlassian_apis: ^0.8.0

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:atlassian_apis/confluence.dart';
import 'package:atlassian_apis/jira_platform.dart';
import 'package:atlassian_apis/jira_software.dart';
import 'package:atlassian_apis/service_management.dart'; 

example/main.dart

import 'dart:io';
import 'package:atlassian_apis/jira_platform.dart';

void main() async {
  // This example uses API Token authentication.
  // Alternatively, you can use OAuth.
  var user = Platform.environment['ATLASSIAN_USER']!;
  var apiToken = Platform.environment['ATLASSIAN_API_TOKEN']!;

  // Create an authenticated http client.
  var client = ApiClient.basicAuthentication(
      Uri.https('<your-account>.atlassian.net', ''),
      user: user,
      apiToken: apiToken);

  // Create the API wrapper from the http client
  var jira = JiraPlatformApi(client);

  // Communicate with the APIs..
  await jira.projects.searchProjects();

  // Close the client to quickly terminate the process
  client.close();
} 

Download Details:

Author: xvrh

Source Code: https://github.com/xvrh/atlassian_apis

#dart #rest #android #ios 

A Client for The Atlassian REST APIs in Dart
Hans  Marvin

Hans Marvin

1660311196

How to Build REST Endpoints with Knex & PostgreSQL Easily

Knex is a versatile, portable, and enjoyable SQL query builder for PostgreSQL, CockroachDB, MSSQL, MySQL, MariaDB, SQLite3, Better-SQLite3, Oracle, and Amazon Redshift, while PostgreSQL is an open-source object-relational database management system with a high degree of flexibility. It is capable of handling a wide range of use cases, including single machines, data warehouses, and web services with multiple concurrent users. It is a relational database management system that uses and extends SQL (hence the name), and it is broadly extensible to a variety of use cases beyond transactional data.

PostgreSQL stores information in tables (called relations) that contain tuples that represent entities (like documents and people) and relationships (such as authorship). Fixed-type attributes that represent entity properties (such as a title), as well as a primary key, are included in relationships. Attribute types can be atomic (for example, integer, floating point, or boolean) or structured (such as an array, nested JSON, or a procedure).

In this blog, we will tell you how you should go about building REST-based services using Knex and PostgreSQL.

See more at: https://arctype.com/blog/postgresql-rest-api-knex/

#rest #postgresql #knex

How to Build REST Endpoints with Knex & PostgreSQL Easily
Elian  Harber

Elian Harber

1659960420

Cete: A Distributed Key Value Store Server Written in Go

Cete

Cete is a distributed key value store server written in Go built on top of BadgerDB.
It provides functions through gRPC (HTTP/2 + Protocol Buffers) or traditional RESTful API (HTTP/1.1 + JSON).
Cete implements Raft consensus algorithm by hashicorp/raft. It achieve consensus across all the instances of the nodes, ensuring that every change made to the system is made to a quorum of nodes, or none at all.
Cete makes it easy bringing up a cluster of BadgerDB (a cete of badgers) .

Features

  • Easy deployment
  • Bringing up cluster
  • Database replication
  • An easy-to-use HTTP API
  • CLI is also available
  • Docker container image is available

Building Cete

When you satisfied dependencies, let's build Cete for Linux as following:

$ mkdir -p ${GOPATH}/src/github.com/mosuka
$ cd ${GOPATH}/src/github.com/mosuka
$ git clone https://github.com/mosuka/cete.git
$ cd cete
$ make build

If you want to build for other platform, set GOOS, GOARCH environment variables. For example, build for macOS like following:

$ make GOOS=darwin build

Binaries

You can see the binary file when build successful like so:

$ ls ./bin
cete

Testing Cete

If you want to test your changes, run command like following:

$ make test

Packaging Cete

Linux

$ make GOOS=linux dist

macOS

$ make GOOS=darwin dist

Configure Cete

CLI FlagEnvironment variableConfiguration FileDescription
--config-file--config file. if omitted, cete.yaml in /etc and home directory will be searched
--idCETE_IDidnode ID
--raft-addressCETE_RAFT_ADDRESSraft_addressRaft server listen address
--grpc-addressCETE_GRPC_ADDRESSgrpc_addressgRPC server listen address
--http-addressCETE_HTTP_ADDRESShttp_addressHTTP server listen address
--data-directoryCETE_DATA_DIRECTORYdata_directorydata directory which store the key-value store data and Raft logs
--peer-grpc-addressCETE_PEER_GRPC_ADDRESSpeer_grpc_addresslisten address of the existing gRPC server in the joining cluster
--certificate-fileCETE_CERTIFICATE_FILEcertificate_filepath to the client server TLS certificate file
--key-fileCETE_KEY_FILEkey_filepath to the client server TLS key file
--common-nameCETE_COMMON_NAMEcommon_namecertificate common name
--log-levelCETE_LOG_LEVELlog_levellog level
--log-fileCETE_LOG_FILElog_filelog file
--log-max-sizeCETE_LOG_MAX_SIZElog_max_sizemax size of a log file in megabytes
--log-max-backupsCETE_LOG_MAX_BACKUPSlog_max_backupsmax backup count of log files
--log-max-ageCETE_LOG_MAX_AGElog_max_agemax age of a log file in days
--log-compressCETE_LOG_COMPRESSlog_compresscompress a log file

Starting Cete node

Starting cete is easy as follows:

$ ./bin/cete start --id=node1 --raft-address=:7000 --grpc-address=:9000 --http-address=:8000 --data-directory=/tmp/cete/node1

You can get the node information with the following command:

$ ./bin/cete node | jq .

or the following URL:

$ curl -X GET http://localhost:8000/v1/node | jq .

The result of the above command is:

{
  "node": {
    "raft_address": ":7000",
    "metadata": {
      "grpc_address": ":9000",
      "http_address": ":8000"
    },
    "state": "Leader"
  }
}

Health check

You can check the health status of the node.

$ ./bin/cete healthcheck | jq .

Also provides the following REST APIs

Liveness prove

This endpoint always returns 200 and should be used to check Cete health.

$ curl -X GET http://localhost:8000/v1/liveness_check | jq .

Readiness probe

This endpoint returns 200 when Cete is ready to serve traffic (i.e. respond to queries).

$ curl -X GET http://localhost:8000/v1/readiness_check | jq .

Putting a key-value

To put a key-value, execute the following command:

$ ./bin/cete set 1 value1

or, you can use the RESTful API as follows:

$ curl -X PUT 'http://127.0.0.1:8000/v1/data/1' --data-binary value1
$ curl -X PUT 'http://127.0.0.1:8000/v1/data/2' -H "Content-Type: image/jpeg" --data-binary @/path/to/photo.jpg

Getting a key-value

To get a key-value, execute the following command:

$ ./bin/cete get 1

or, you can use the RESTful API as follows:

$ curl -X GET 'http://127.0.0.1:8000/v1/data/1'

You can see the result. The result of the above command is:

value1

Deleting a key-value

Deleting a value by key, execute the following command:

$ ./bin/cete delete 1

or, you can use the RESTful API as follows:

$ curl -X DELETE 'http://127.0.0.1:8000/v1/data/1'

Bringing up a cluster

Cete is easy to bring up the cluster. Cete node is already running, but that is not fault tolerant. If you need to increase the fault tolerance, bring up 2 more data nodes like so:

$ ./bin/cete start --id=node2 --raft-address=:7001 --grpc-address=:9001 --http-address=:8001 --data-directory=/tmp/cete/node2 --peer-grpc-address=:9000
$ ./bin/cete start --id=node3 --raft-address=:7002 --grpc-address=:9002 --http-address=:8002 --data-directory=/tmp/cete/node3 --peer-grpc-address=:9000

Above example shows each Cete node running on the same host, so each node must listen on different ports. This would not be necessary if each node ran on a different host.

This instructs each new node to join an existing node, each node recognizes the joining clusters when started. So you have a 3-node cluster. That way you can tolerate the failure of 1 node. You can check the cluster with the following command:

$ ./bin/cete cluster | jq .

or, you can use the RESTful API as follows:

$ curl -X GET 'http://127.0.0.1:8000/v1/cluster' | jq .

You can see the result in JSON format. The result of the above command is:

{
  "cluster": {
    "nodes": {
      "node1": {
        "raft_address": ":7000",
        "metadata": {
          "grpc_address": ":9000",
          "http_address": ":8000"
        },
        "state": "Leader"
      },
      "node2": {
        "raft_address": ":7001",
        "metadata": {
          "grpc_address": ":9001",
          "http_address": ":8001"
        },
        "state": "Follower"
      },
      "node3": {
        "raft_address": ":7002",
        "metadata": {
          "grpc_address": ":9002",
          "http_address": ":8002"
        },
        "state": "Follower"
      }
    },
    "leader": "node1"
  }
}

Recommend 3 or more odd number of nodes in the cluster. In failure scenarios, data loss is inevitable, so avoid deploying single nodes.

The above example, the node joins to the cluster at startup, but you can also join the node that already started on standalone mode to the cluster later, as follows:

$ ./bin/cete join --grpc-addr=:9000 node2 127.0.0.1:9001

or, you can use the RESTful API as follows:

$ curl -X PUT 'http://127.0.0.1:8000/v1/cluster/node2' --data-binary '
{
  "raft_address": ":7001",
  "metadata": {
    "grpc_address": ":9001",
    "http_address": ":8001"
  }
}
'

To remove a node from the cluster, execute the following command:

$ ./bin/cete leave --grpc-addr=:9000 node2

or, you can use the RESTful API as follows:

$ curl -X DELETE 'http://127.0.0.1:8000/v1/cluster/node2'

The following command indexes documents to any node in the cluster:

$ ./bin/cete set 1 value1 --grpc-address=:9000 

So, you can get the document from the node specified by the above command as follows:

$ ./bin/cete get 1 --grpc-address=:9000

You can see the result. The result of the above command is:

value1

You can also get the same document from other nodes in the cluster as follows:

$ ./bin/cete get 1 --grpc-address=:9001
$ ./bin/cete get 1 --grpc-address=:9002

You can see the result. The result of the above command is:

value1

Cete on Docker

Building Cete Docker container image on localhost

You can build the Docker container image like so:

$ make docker-build

Pulling Cete Docker container image from docker.io

You can also use the Docker container image already registered in docker.io like so:

$ docker pull mosuka/cete:latest

See https://hub.docker.com/r/mosuka/cete/tags/

Pulling Cete Docker container image from docker.io

You can also use the Docker container image already registered in docker.io like so:

$ docker pull mosuka/cete:latest

Running Cete node on Docker

Running a Cete data node on Docker. Start Cete node like so:

$ docker run --rm --name cete-node1 \
    -p 7000:7000 \
    -p 8000:8000 \
    -p 9000:9000 \
    mosuka/cete:latest cete start \
      --id=node1 \
      --raft-address=:7000 \
      --grpc-address=:9000 \
      --http-address=:8000 \
      --data-directory=/tmp/cete/node1

You can execute the command in docker container as follows:

$ docker exec -it cete-node1 cete node --grpc-address=:9000

Securing Cete

Cete supports HTTPS access, ensuring that all communication between clients and a cluster is encrypted.

Generating a certificate and private key

One way to generate the necessary resources is via openssl. For example:

$ openssl req -x509 -nodes -newkey rsa:4096 -keyout ./etc/cete-key.pem -out ./etc/cete-cert.pem -days 365 -subj '/CN=localhost'
Generating a 4096 bit RSA private key
............................++
........++
writing new private key to 'key.pem'

Secure cluster example

Starting a node with HTTPS enabled, node-to-node encryption, and with the above configuration file. It is assumed the HTTPS X.509 certificate and key are at the paths server.crt and key.pem respectively.

$ ./bin/cete start --id=node1 --raft-address=:7000 --grpc-address=:9000 --http-address=:8000 --data-directory=/tmp/cete/node1 --peer-grpc-address=:9000 --certificate-file=./etc/cert.pem --key-file=./etc/key.pem --common-name=localhost
$ ./bin/cete start --id=node2 --raft-address=:7001 --grpc-address=:9001 --http-address=:8001 --data-directory=/tmp/cete/node2 --peer-grpc-address=:9000 --certificate-file=./etc/cert.pem --key-file=./etc/key.pem --common-name=localhost
$ ./bin/cete start --id=node3 --raft-address=:7002 --grpc-address=:9002 --http-address=:8002 --data-directory=/tmp/cete/node3 --peer-grpc-address=:9000 --certificate-file=./etc/cert.pem --key-file=./etc/key.pem --common-name=localhost

You can access the cluster by adding a flag, such as the following command:

$ ./bin/cete cluster --grpc-address=:9000 --certificate-file=./cert.pem --common-name=localhost | jq .

or

$ curl -X GET https://localhost:8000/v1/cluster --cacert ./cert.pem | jq .

Author: Mosuka
Source Code: https://github.com/mosuka/cete 
License: Apache-2.0 license

#go #golang #rest #grpc 

Cete: A Distributed Key Value Store Server Written in Go
Nat  Grady

Nat Grady

1657898040

Insomnia: The Open-source, Cross-platform API Client for GraphQL, REST

Insomnia API Client

Insomnia is an open-source, cross-platform API client for GraphQL, REST, and gRPC.

Insomnia API Client

Develop Insomnia

Development on Insomnia can be done on Mac, Windows, or Linux as long as you have Node.js and Git. See the .nvmrc file located in the project for the correct Node version.

Initial Dev Setup

This repository is structured as a monorepo and contains many Node.JS packages. Each package has its own set of commands, but the most common commands are available from the root package.json and can be accessed using the npm run … command. Here are the only three commands you should need to start developing on the app.

# Install and Link Dependencies
npm run bootstrap

# Run Tests
npm test

# Start App with Live Reload
npm run app-start

Linux

If you are on Linux, you may need to install the following supporting packages:

Ubuntu/Debian

# Update library
sudo apt-get update

# Install font configuration library & support
sudo apt-get install libfontconfig-dev

Fedora

# Install libcurl for node-libcurl
sudo dnf install libcurl-devel

Also on Linux, if Electron is failing during the bootstrap process, run the following

# Clear Electron install conflicts
rm -rf ~/.cache/electron

Windows

If you are on Windows and have problems, you may need to install Windows Build Tools

Editor Requirements

You can use any editor you'd like, but make sure to have support/plugins for the following tools:

  • ESLint - For catching syntax problems and common errors
  • JSX Syntax - For React components

Develop Inso CLI

  • Bootstrap: npm run bootstrap
  • Start the compiler in watch mode: npm run inso-start
  • Run: ./packages/insomnia-inso/bin/inso -v

Plugins

Search for, discover, and install plugins from the Insomnia Plugin Hub!

Download

Insomnia is available for Mac, Windows, and Linux and can be downloaded from the website.

https://insomnia.rest/download

Bugs and Feature Requests

Have a bug or a feature request? First, read the issue guidelines and search for existing and closed issues. If your problem or idea is not addressed yet, please open a new issue.

For more generic product questions and feedback, join the Slack Team or email support@insomnia.rest

Contributing

Please read through our contributing guidelines and code of conduct. Included are directions for opening issues, coding standards, and notes on development.

Documentation

Check out our open-source Insomnia Documentation.

Community Projects

Author: Kong
Source Code: https://github.com/Kong/insomnia 
License: MIT license

#electron #api #graphql #grpc #rest 

Insomnia: The Open-source, Cross-platform API Client for GraphQL, REST

PHP-jira-rest-client: PHP Classes interact Jira with The REST API

PHP JIRA Rest Client   

Requirements

Installation

Download and Install PHP Composer.

curl -sS https://getcomposer.org/installer | php

Next, run the Composer command to install the latest version of php jira rest client.

php composer.phar require lesstif/php-jira-rest-client

or add the following to your composer.json file.

{
    'require': {
        'lesstif/php-jira-rest-client': '^4.0'
    }
}

Then run Composer's install or update commands to complete installation.

php composer.phar install

After installing, you need to require Composer's autoloader:

require 'vendor/autoload.php';

Laravel: Once installed, if you are not using automatic package discovery, then you need to register the JiraRestApi\JiraRestApiServiceProvider service provider in your config/app.php.

Configuration

you can choose loads environment variables either 'dotenv' or 'array'.

use dotenv

copy .env.example file to .env on your project root.

JIRA_HOST='https://your-jira.host.com'
JIRA_USER='jira-username'
JIRA_PASS='jira-password-OR-api-token'
# if TOKEN_BASED_AUTH set to true, ignore JIRA_USER and JIRA_PASS.
TOKEN_BASED_AUTH=true
PERSONAL_ACCESS_TOKEN='your-access-token-here'
# to enable session cookie authorization
# COOKIE_AUTH_ENABLED=true
# COOKIE_FILE=storage/jira-cookie.txt
# if you are behind a proxy, add proxy settings
PROXY_SERVER='your-proxy-server'
PROXY_PORT='proxy-port'
PROXY_USER='proxy-username'
PROXY_PASSWORD='proxy-password'
JIRA_REST_API_V3=false

Important Note: As of March 15, 2018, in accordance to the Atlassian REST API Policy, Basic auth with password to be deprecated. Instead of password, you should using API token.

Laravel Users: If you are developing with laravel framework(5.x), you must append above configuration to your application .env file.

REST API V3 Note: In accordance to the Atlassian's deprecation notice, After the 29th of april 2019, REST API no longer supported username and userKey, and instead use the account ID. if you are JIRA Cloud users, you need to set JIRA_REST_API_V3=true in the .env file.

CAUTION this library not fully supported JIRA REST API V3 yet.

use array

create Service class with ArrayConfiguration parameter.

use JiraRestApi\Configuration\ArrayConfiguration;
use JiraRestApi\Issue\IssueService;

$iss = new IssueService(new ArrayConfiguration(
          [
               'jiraHost' => 'https://your-jira.host.com',
                // Basic authentication deprecated 
                /*                 
                 'jiraUser' => 'jira-username',
                'jiraPassword' => 'jira-password-OR-api-token',
                */
               // instead,you can use the token based authentication. 
               'useV3RestApi' => false,
               'useTokenBasedAuth' => true,
               'personalAccessToken' => 'your-token-here',
                
                // custom log config
               'jiraLogEnabled' => true,
               'jiraLogFile' => "my-jira-rest-client.log",
               'jiraLogLevel' => 'INFO',
        
               // to enable session cookie authorization (with basic authorization only)
               'cookieAuthEnabled' => true,
               'cookieFile' => storage_path('jira-cookie.txt'),
               // if you are behind a proxy, add proxy settings
               'proxyServer' => 'your-proxy-server',
               'proxyPort' => 'proxy-port',
               'proxyUser' => 'proxy-username',
               'proxyPassword' => 'proxy-password',
          ]
   ));

Usage

Table of Contents

Project

Custom Field

Issue

Comment

IssueLink

User

Group

Priority

Attachment

Version

Component

Board

Epic

Create Project

Create a new project.

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Project\ProjectService;
use JiraRestApi\Project\Project;
use JiraRestApi\JiraException;

try {
    $p = new Project();

    $p->setKey('EX')
        ->setName('Example')
        ->setProjectTypeKey('business')
        ->setProjectTemplateKey('com.atlassian.jira-core-project-templates:jira-core-project-management')
        ->setDescription('Example Project description')
        ->setLeadName('lesstif')
        ->setUrl('http://example.com')
        ->setAssigneeType('PROJECT_LEAD')
        ->setAvatarId(10130)
        ->setIssueSecurityScheme(10000)
        ->setPermissionScheme(10100)
        ->setNotificationScheme(10100)
        ->setCategoryId(10100)
    ;

    $proj = new ProjectService();

    $pj = $proj->createProject($p);
   
    // 'http://example.com/rest/api/2/project/10042'
    var_dump($pj->self);
    // 10042 
    var_dump($pj->id);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Update Project

Update a project. Only non null values sent in JSON will be updated in the project.

Values available for the assigneeType field are: 'PROJECT_LEAD' and 'UNASSIGNED'.

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Project\ProjectService;
use JiraRestApi\Project\Project;
use JiraRestApi\JiraException;

try {
    $p = new Project();

    $p->setName('Updated Example')
        ->setProjectTypeKey('software')
        ->setProjectTemplateKey('com.atlassian.jira-software-project-templates:jira-software-project-management')
        ->setDescription('Updated Example Project description')
        ->setLead('new-leader')
        ->setUrl('http://new.example.com')
        ->setAssigneeType('UNASSIGNED')
    ;

    $proj = new ProjectService();

    $pj = $proj->updateProject($p, 'EX');
   
    var_dump($pj);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Delete Project

Deletes a project.

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $proj = new ProjectService();

    $pj = $proj->deleteProject('EX');
   
    var_dump($pj);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get Project Info

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $proj = new ProjectService();

    $p = $proj->get('TEST');
    
    var_dump($p);            
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get All Project list

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $proj = new ProjectService();

    $prjs = $proj->getAllProjects();

    foreach ($prjs as $p) {
        echo sprintf('Project Key:%s, Id:%s, Name:%s, projectCategory: %s\n',
            $p->key, $p->id, $p->name, $p->projectCategory['name']
        );            
    }            
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get Project Components

See Jira API reference (Get project components)

<?php
require 'vendor/autoload.php';

use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $proj = new ProjectService();

    $prjs = $proj->getAllProjects();

    // Extract and show Project Components for every Jira Project
    foreach ($prjs as $p) {
        var_export($proj->getProjectComponents($p->id));
    }
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get Project type

See Jira API reference (get all types)

See Jira API reference (get type)

<?php
require 'vendor/autoload.php';

use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $proj = new ProjectService();

    // get all project type
    $prjtyps = $proj->getProjectTypes();

    foreach ($prjtyps as $pt) {
        var_dump($pt);
    }

    // get specific project type.
    $pt = $proj->getProjectType('software');
    var_dump($pt);

} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get Project Version

get all project's versions.

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $proj = new ProjectService();

    $vers = $proj->getVersions('TEST');

    foreach ($vers as $v) {
        // $v is  JiraRestApi\Issue\Version
        var_dump($v);
    }
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

or get pagenated project's versions.

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $param = [
        'startAt' => 0,
        'maxResults' => 10,
        'orderBy' => 'name',
        //'expand' => null,
    ];

    $proj = new ProjectService();

    $vers = $proj->getVersionsPagenated('TEST', $param);

    foreach ($vers as $v) {
        // $v is  JiraRestApi\Issue\Version
        var_dump($v);
    }
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get All Field List

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Field\Field;
use JiraRestApi\Field\FieldService;
use JiraRestApi\JiraException;

try {
    $fieldService = new FieldService();

    // return custom field only. 
    $ret = $fieldService->getAllFields(Field::CUSTOM); 
        
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'testSearch Failed : '.$e->getMessage());
}

Create Custom Field

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Field\Field;
use JiraRestApi\Field\FieldService;
use JiraRestApi\JiraException;

try {
    $field = new Field();
    
    $field->setName('New custom field')
            ->setDescription('Custom field for picking groups')
            ->setType('com.atlassian.jira.plugin.system.customfieldtypes:grouppicker')
            ->setSearcherKey('com.atlassian.jira.plugin.system.customfieldtypes:grouppickersearcher');

    $fieldService = new FieldService();

    $ret = $fieldService->create($field);
    
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'Field Create Failed : '.$e->getMessage());
}

If you need a list of custom field types(ex. com.atlassian.jira.plugin.system.customfieldtypes:grouppicker) , check out Get All Field list.

Get Issue Info

See Jira API reference

Returns a full representation of the issue for the given issue key.

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

try {
    $issueService = new IssueService();
    
    $queryParam = [
        'fields' => [  // default: '*all'
            'summary',
            'comment',
        ],
        'expand' => [
            'renderedFields',
            'names',
            'schema',
            'transitions',
            'operations',
            'editmeta',
            'changelog',
        ]
    ];
            
    $issue = $issueService->get('TEST-867', $queryParam);
    
    var_dump($issue->fields);    
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

You can access the custom field associated with issue through $issue->fields->customFields array or through direct custom field id variables(Ex: $issue->fields->customfield_10300).

Create Issue

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\IssueField;
use JiraRestApi\JiraException;

try {
    $issueField = new IssueField();

    $issueField->setProjectKey('TEST')
                ->setSummary('something's wrong')
                ->setAssigneeNameAsString('lesstif')
                ->setPriorityNameAsString('Critical')
                ->setIssueTypeAsString('Bug')
                ->setDescription('Full description for issue')
                ->addVersionAsString('1.0.1')
                ->addVersionAsArray(['1.0.2', '1.0.3'])
                ->addComponentsAsArray(['Component-1', 'Component-2'])
                // set issue security if you need.
                ->setSecurityId(10001 /* security scheme id */)
                ->setDueDateAsString('2023-06-19')
                // or you can use DateTimeInterface
                //->setDueDateAsDateTime(
                //            (new DateTime('NOW'))->add(DateInterval::createFromDateString('1 month 5 day'))
                // )
            ;
    
    $issueService = new IssueService();

    $ret = $issueService->create($issueField);
    
    //If success, Returns a link to the created issue.
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

If you want to set custom field, you can call the addCustomField function with custom field id and value as parameters.

try {
    $issueField = new IssueField();

    $issueField->setProjectKey('TEST')
                ->setSummary('something's wrong')
                ->setAssigneeNameAsString('lesstif')
                ->setPriorityNameAsString('Critical')
                ->setIssueTypeAsString('Bug')
                ->setDescription('Full description for issue')
                ->addVersion('1.0.1')
                ->addVersion('1.0.3')
                ->addCustomField('customfield_10100', 'text area body text') // String type custom field
                ->addCustomField('customfield_10200', ['value' => 'Linux']) // Select List (single choice)
                ->addCustomField('customfield_10408', [
                    ['value' => 'opt2'], ['value' => 'opt4']
                ]) // Select List (multiple choice)
    ;
    
    $issueService = new IssueService();

    $ret = $issueService->create($issueField);
    
    //If success, Returns a link to the created issue.
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Currently, not tested for all custom field types.

Create Multiple Issues

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\IssueField;
use JiraRestApi\JiraException;

try {
    $issueFieldOne = new IssueField();

    $issueFieldOne->setProjectKey('TEST')
                ->setSummary('something's wrong')
                ->setPriorityNameAsString('Critical')
                ->setIssueTypeAsString('Bug')
                ->setDescription('Full description for issue');

    $issueFieldTwo = new IssueField();

    $issueFieldTwo->setProjectKey('TEST')
                ->setSummary('something else is wrong')
                ->setPriorityNameAsString('Critical')
                ->setIssueTypeAsString('Bug')
                ->setDescription('Full description for second issue');
    
    $issueService = new IssueService();

    $ret = $issueService->createMultiple([$issueFieldOne, $issueFieldTwo]);
    
    //If success, returns an array of the created issues
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Create Sub Task

See Jira API reference

Creating a sub-task is similar to creating a regular issue, with two important method calls:

->setIssueTypeAsString('Sub-task')
->setParentKeyOrId($issueKeyOrId)

for example ​

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\IssueField;
use JiraRestApi\JiraException;

try {
    $issueField = new IssueField();

    $issueField->setProjectKey('TEST')
                ->setSummary('something's wrong')
                ->setAssigneeNameAsString('lesstif')
                ->setPriorityNameAsString('Critical')
                ->setDescription('Full description for issue')
                ->addVersion('1.0.1')
                ->addVersion('1.0.3')
                ->setIssueTypeAsString('Sub-task')  //issue type must be Sub-task
                ->setParentKeyOrId('TEST-143')  //Issue Key
    ;

    $issueService = new IssueService();

    $ret = $issueService->create($issueField);

    //If success, Returns a link to the created sub task.
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Create Issue using REST API V3

REST API V3' description field is complicated.

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\IssueFieldV3;
use JiraRestApi\JiraException;

try {
    $issueField = new IssueFieldV3();

    $paraDesc =<<< DESC

Full description for issue
- order list 1
- order list 2
-- sub order list 1
-- sub order list 1
- order list 3 
DESC;
    
    $issueField->setProjectKey('TEST')
                ->setSummary('something's wrong')
                ->setAssigneeAccountId('user-account-id-here')
                ->setPriorityNameAsString('Critical')
                ->setIssueTypeAsString('Bug')
                ->addDescriptionHeading(3, 'level 3 heading here')
                ->addDescriptionParagraph($paraDesc)
                ->addVersion(['1.0.1', '1.0.3'])
                ->addComponents(['Component-1', 'Component-2'])
                // set issue security if you need.
                ->setDueDateAsString('2019-06-19')
            ;
    
    $issueService = new IssueService();

    $ret = $issueService->create($issueField);
    
    //If success, Returns a link to the created issue.
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

If you want to set custom field, you can call the addCustomField function with custom field id and value as parameters.

try {
    $issueField = new IssueField();

    $issueField->setProjectKey('TEST')
                ->setSummary('something's wrong')
                ->setAssigneeNameAsString('lesstif')
                ->setPriorityNameAsString('Critical')
                ->setIssueTypeAsString('Bug')
                ->setDescription('Full description for issue')
                ->addVersion('1.0.1')
                ->addVersion('1.0.3')
                ->addCustomField('customfield_10100', 'text area body text') // String type custom field
                ->addCustomField('customfield_10200', ['value' => 'Linux']) // Select List (single choice)
                ->addCustomField('customfield_10408', [
                    ['value' => 'opt2'], ['value' => 'opt4']
                ]) // Select List (multiple choice)
    ;
    
    $issueService = new IssueService();

    $ret = $issueService->create($issueField);
    
    //If success, Returns a link to the created issue.
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Currently, not tested for all custom field types.

Add Attachment

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {
    $issueService = new IssueService();

    // multiple file upload support.
    $ret = $issueService->addAttachments($issueKey, 
        ['screen_capture.png', 'bug-description.pdf', 'README.md']
    );

    print_r($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(FALSE, 'Attach Failed : ' . $e->getMessage());
}

Update issue

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\IssueField;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {            
    $issueField = new IssueField(true);

    $issueField->setAssigneeNameAsString('admin')
                ->setPriorityNameAsString('Blocker')
                ->setIssueTypeAsString('Task')
                ->addLabel('test-label-first')
                ->addLabel('test-label-second')
                ->addVersion('1.0.1')
                ->addVersion('1.0.2')
                ->setDescription('This is a shorthand for a set operation on the summary field')
    ;

    // optionally set some query params
    $editParams = [
        'notifyUsers' => false,
    ];

    $issueService = new IssueService();

    // You can set the $paramArray param to disable notifications in example
    $ret = $issueService->update($issueKey, $issueField, $editParams);

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(FALSE, 'update Failed : ' . $e->getMessage());
}

If you want to change the custom field type when updating an issue, you can call the addCustomField function just as you did for creating issue.

Update labels

This function is a convenient wrapper for add or remove label in the issue.

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

try {
    $issueKey = 'TEST-123';

    $issueService = new IssueService();

    $addLabels = [
        'triaged', 'customer-request', 'sales-request'
    ];

    $removeLabel = [
        'will-be-remove', 'this-label-is-typo'
    ];

    $ret = $issueService->updateLabels($issueKey,
            $addLabels,
            $removeLabel,
            $notifyUsers = false
        );

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'updateLabels Failed : '.$e->getMessage());
}

Update fix versions

This function is a convenient wrapper for add or remove fix version in the issue.

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

try {
    $issueKey = 'TEST-123';

    $issueService = new IssueService();

    $addVersions = [
        '1.1.1', 'named-version'
    ];

    $removeVersions = [
        '1.1.0', 'old-version'
    ];

    $ret = $issueService->updateFixVersions($issueKey,
            $addVersions,
            $removeVersions,
            $notifyUsers = false
        );

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'updateFixVersions Failed : '.$e->getMessage());
}

Change Assignee

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {
    $issueService = new IssueService();

    // if assignee is -1, automatic assignee used.
    // A null assignee will remove the assignee.
    $assignee = 'newAssigneeName';

    $ret = $issueService->changeAssignee($issueKey, $assignee);

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(FALSE, 'Change Assignee Failed : ' . $e->getMessage());
}

REST API V3(JIRA Cloud) users must use changeAssigneeByAccountId method with accountId.

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {
    $issueService = new IssueService();

    $accountId = 'usre-account-id';

    $ret = $issueService->changeAssigneeByAccountId($issueKey, $accountId);

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(FALSE, 'Change Assignee Failed : ' . $e->getMessage());
}

Remove Issue

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {
    $issueService = new IssueService();

    $ret = $issueService->deleteIssue($issueKey);
    // if you want to delete issues with sub-tasks
    //$ret = $issueService->deleteIssue($issueKey, array('deleteSubtasks' => 'true'));

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(FALSE, 'Remove Issue Failed : ' . $e->getMessage());
}

Add comment

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\Comment;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {            
    $comment = new Comment();

    $body = <<<COMMENT
Adds a new comment to an issue.
* Bullet 1
* Bullet 2
** sub Bullet 1
** sub Bullet 2
* Bullet 3
COMMENT;

    $comment->setBody($body)
        ->setVisibilityAsString('role', 'Users');
    ;

    $issueService = new IssueService();
    $ret = $issueService->addComment($issueKey, $comment);
    print_r($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(FALSE, 'add Comment Failed : ' . $e->getMessage());
}

Get comment

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {
    $issueService = new IssueService();
    
    $param = [
         'startAt' => 0, 
         'maxResults' => 3,
         'expand' => 'renderedBody',
    ];
   
    $comments = $issueService->getComments($issueKey, $param);

    var_dump($comments);

} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'get Comment Failed : '.$e->getMessage());
}

get comment by comment id

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {
    $issueService = new IssueService();
    
    $param = [
         'startAt' => 0, 
         'maxResults' => 3,
         'expand' => 'renderedBody',
    ];
    $commentId = 13805;

    $comments = $issueService->getComment($issueKey, $commentId, $param);

    var_dump($comments);

} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'get Comment Failed : '.$e->getMessage());
}

Delete comment

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {
    $commentId = 12345;

    $issueService = new IssueService();

    $ret = $issueService->deleteComment($issueKey, $commentId);

} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'Delete comment Failed : '.$e->getMessage());
}

Update comment

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;
use JiraRestApi\Issue\Comment;

$issueKey = 'TEST-879';

try {
    $commentId = 12345;

    $issueService = new IssueService();
        
    $comment = new Comment();
    $comment->setBody('Updated comments');
    
    $issueService->updateComment($issueKey, $commentId, $comment);

} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'Update comment Failed : '.$e->getMessage());
}

Perform a transition on an issue

Note: this library uses goal status names instead of transition names. So, if you want to change issue status to 'Some Status', you should pass that status name to setTransitionName

i.e. $transition->setTransitionName('Some Status')

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\Transition;
use JiraRestApi\JiraException;

$issueKey = 'TEST-879';

try {            
    $transition = new Transition();
    $transition->setTransitionName('Resolved');
    $transition->setCommentBody('performing the transition via REST API.');

    $issueService = new IssueService();

    $issueService->transition($issueKey, $transition);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(FALSE, 'add Comment Failed : ' . $e->getMessage());
}

Note: If you are JIRA with local language profiles, you must use setUntranslatedName instead of setTransitionName.

i.e. $transition->setUntranslatedName('Done')

Perform an advanced search

Simple Query

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$jql = 'project not in (TEST)  and assignee = currentUser() and status in (Resolved, closed)';

try {
    $issueService = new IssueService();

    $ret = $issueService->search($jql);
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'testSearch Failed : '.$e->getMessage());
}

Simple Query with LinkedIssue

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;
use JiraRestApi\Issue\JqlFunction;

// Searches for issues that are linked to an issue. You can restrict the search to links of a particular type. 
try {
    $linkedIssue = JqlFunction::linkedIssues('TEST-01', 'IN', 'is blocked by');

    $issueService = new IssueService();

    $ret = $issueService->search($linkedIssue->expression);

    var_dump($ret);
} catch (JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

// Searches for epics and subtasks. If the issue is not an epic, the search returns all subtasks for the issue. 
try {
    $linkedIssue = JqlFunction::linkedissue('TEST-01');

    $issueService = new IssueService();

    $ret = $issueService->search($linkedIssue->expression);

    var_dump($ret);
} catch (JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

JQL with pagination

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$jql = 'project not in (TEST)  and assignee = currentUser() and status in (Resolved, closed)';

try {
    $issueService = new IssueService();

    $pagination = -1;
  
    $startAt = 0;    //the index of the first issue to return (0-based)    
    $maxResult = 3;    // the maximum number of issues to return (defaults to 50). 
    $totalCount = -1;    // the number of issues to return
  
    // first fetch
    $ret = $issueService->search($jql, $startAt, $maxResult);
    $totalCount = $ret->total;
      
    // do something with fetched data
    foreach ($ret->issues as $issue) {
        print (sprintf('%s %s \n', $issue->key, $issue->fields->summary));
    }
      
    // fetch remained data
    $page = $totalCount / $maxResult;

    for ($startAt = 1; $startAt < $page; $startAt++) {
        $ret = $issueService->search($jql, $startAt * $maxResult, $maxResult);

        print ('\nPaging $startAt\n');
        print ('-------------------\n');
        foreach ($ret->issues as $issue) {
            print (sprintf('%s %s \n', $issue->key, $issue->fields->summary));
        }
    }     
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'testSearch Failed : '.$e->getMessage());
}

JQL query class

See Jira API reference

If you're not familiar JQL then you can use convenience JqlQuery class. JqlFunction class can be used to add jql functions calls to query. You can find the names of almost all fields, functions, keywords and operators defined as constants in JqlQuery and static methods in JqlFunciton classes. For more info see the Jira docs (link above).

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\JqlQuery;
use JiraRestApi\JiraException;
use JiraRestApi\Issue\JqlFunction;

try {
    $jql = new JqlQuery();

    $jql->setProject('TEST')
        ->setType('Bug')
        ->setStatus('In Progress')
        ->setAssignee(JqlFunction::currentUser())
        ->setCustomField('My Custom Field', 'value')
        ->addIsNotNullExpression('due');

    $issueService = new IssueService();

    $ret = $issueService->search($jql->getQuery());

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'testSearch Failed : '.$e->getMessage());
}

Remote Issue Link

get remote issue link

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-316';

try {
    $issueService = new IssueService();

    $rils = $issueService->getRemoteIssueLink($issueKey);
        
    // rils is array of RemoteIssueLink classes
    var_dump($rils);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, $e->getMessage());
}

create remote issue link

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\RemoteIssueLink;
use JiraRestApi\JiraException;

$issueKey = 'TEST-316';

try {
    $issueService = new IssueService();

    $ril = new RemoteIssueLink();

    $ril->setUrl('http://www.mycompany.com/support?id=1')
        ->setTitle('Remote Link Title')
        ->setRelationship('causes')
        ->setSummary('Crazy customer support issue')
    ;

    $rils = $issueService->createOrUpdateRemoteIssueLink($issueKey, $ril);

    // rils is array of RemoteIssueLink classes
    var_dump($rils);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'Create Failed : '.$e->getMessage());
}

Issue time tracking

This methods use get issue and edit issue methods internally.

See Jira API reference (get issue)

See Jira API reference (edit issue)

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\TimeTracking;
use JiraRestApi\JiraException;

$issueKey = 'TEST-961';

try {
    $issueService = new IssueService();
    
    // get issue's time tracking info
    $ret = $issueService->getTimeTracking($this->issueKey);
    var_dump($ret);
    
    $timeTracking = new TimeTracking;

    $timeTracking->setOriginalEstimate('3w 4d 6h');
    $timeTracking->setRemainingEstimate('1w 2d 3h');
    
    // add time tracking
    $ret = $issueService->timeTracking($this->issueKey, $timeTracking);
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'testSearch Failed : '.$e->getMessage());
}

Add worklog in issue

See Jira API V2 reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\Worklog;
use JiraRestApi\JiraException;

$issueKey = 'TEST-961';

try {
    $workLog = new Worklog();

    $workLog->setComment('I did some work here.')
            ->setStarted('2016-05-28 12:35:54')
            ->setTimeSpent('1d 2h 3m');

    $issueService = new IssueService();

    $ret = $issueService->addWorklog($issueKey, $workLog);

    $workLogid = $ret->{'id'};

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'Create Failed : '.$e->getMessage());
}

See Jira API V3 reference

<?php
require 'vendor/autoload.php';

// Worklog example for API V3 assumes JIRA_REST_API_V3=true is configured in
// your .env file.

use JiraRestApi\Issue\ContentField;
use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\Worklog;
use JiraRestApi\JiraException;

$issueKey = 'TEST-961';

try {
    $workLog = new Worklog();

    $paragraph = new ContentField();
    $paragraph->type = 'paragraph';
    $paragraph->content[] = [
        'text' => 'I did some work here.',
        'type' => 'text',
    ];

    $comment = new ContentField();
    $comment->type = 'doc';
    $comment->version = 1;
    $comment->content[] = $paragraph;

    $workLog->setComment($comment)
            ->setStarted('2016-05-28 12:35:54')
            ->setTimeSpent('1d 2h 3m');

    $issueService = new IssueService();

    $ret = $issueService->addWorklog($issueKey, $workLog);

    $workLogid = $ret->{'id'};

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'Create Failed : '.$e->getMessage());
}

edit worklog in issue

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\Worklog;
use JiraRestApi\JiraException;

$issueKey = 'TEST-961';
$workLogid = '12345';

try {
    $workLog = new Worklog();

    $workLog->setComment('I did edit previous worklog here.')
            ->setStarted('2016-05-29 13:15:34')
            ->setTimeSpent('3d 4h 5m');

    $issueService = new IssueService();

    $ret = $issueService->editWorklog($issueKey, $workLog, $workLogid);

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'Edit worklog Failed : '.$e->getMessage());
}

Get issue worklog

See Jira API reference (get full issue worklog)

See Jira API reference (get worklog by id)

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-961';

try {
    $issueService = new IssueService();
    
    // get issue's all worklog
    $worklogs = $issueService->getWorklog($issueKey)->getWorklogs();
    var_dump($worklogs);
    
    // get worklog by id
    $wlId = 12345;
    $wl = $issueService->getWorklogById($issueKey, $wlId);
    var_dump($wl);
    
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'testSearch Failed : '.$e->getMessage());
}

Add watcher to Issue

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-961';

try {
    $issueService = new IssueService();
    
    // watcher's id
    $watcher = 'lesstif';
    
    $issueService->addWatcher($issueKey, $watcher);
    
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'add watcher Failed : '.$e->getMessage());
}

Remove watcher from Issue

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\JiraException;

$issueKey = 'TEST-961';

try {
    $issueService = new IssueService();
    
    // watcher's id
    $watcher = 'lesstif';
    
    $issueService->removeWatcher($issueKey, $watcher);
    
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'add watcher Failed : '.$e->getMessage());
}

issue notify

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\IssueService;
use JiraRestApi\Issue\Notify;
use JiraRestApi\JiraException;

$issueKey = 'TEST-961';

try {
    $issueService = new IssueService();

    $noti = new Notify();

    $noti->setSubject('notify test')
        ->setTextBody('notify test text body')
        ->setHtmlBody('<h1>notify</h1>test html body')
        ->sendToAssignee(true)
        ->sendToWatchers(true)
        ->sendToUser('lesstif', true)
        ->sendToGroup('temp-group')
    ;

    $issueService->notify($issueKey, $noti);
    
} catch (JiraRestApi\JiraException $e) {
    $this->assertTrue(false, 'Issue notify Failed : '.$e->getMessage());
}

Create Issue Link

See Jira API reference

The Link Issue Resource provides functionality to manage issue links.

<?php
require 'vendor/autoload.php';

use JiraRestApi\IssueLink\IssueLink;
use JiraRestApi\IssueLink\IssueLinkService;
use JiraRestApi\JiraException;

try {
    $il = new IssueLink();

    $il->setInwardIssue('TEST-258')
        ->setOutwardIssue('TEST-249')
        ->setLinkTypeName('Relates' )
        ->setComment('Linked related issue via REST API.');
            
    $ils = new IssueLinkService();

    $ret = $ils->addIssueLink($il);

} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get Issue LinkType

See Jira API reference

Rest resource to retrieve a list of issue link types.

<?php
require 'vendor/autoload.php';

use JiraRestApi\IssueLink\IssueLinkService;
use JiraRestApi\JiraException;

try {
    $ils = new IssueLinkService();

    $ret = $ils->getIssueLinkTypes();
    
    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Create User

See Jira API reference

Create user. By default created user will not be notified with email. If password field is not set then password will be randomly generated.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\User\UserService;

try {
    $us = new UserService();

    // create new user
    $user = $us->create([
            'name'=>'charlie',
            'password' => 'abracadabra',
            'emailAddress' => 'charlie@atlassian.com',
            'displayName' => 'Charlie of Atlassian',
        ]);

    var_dump($user);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get User Info

See Jira API reference

Returns a user.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\User\UserService;

try {
    $us = new UserService();

    $user = $us->get(['username' => 'lesstif']);

    var_dump($user);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Find Users

See Jira API reference

Returns a list of users that match the search string and/or property.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\User\UserService;

try {
    $us = new UserService();

    $paramArray = [
        'username' => '.', // get all users. 
        'startAt' => 0,
        'maxResults' => 1000,
        'includeInactive' => true,
        //'property' => '*',
    ];

    // get the user info.
    $users = $us->findUsers($paramArray);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Find Assignable Users

See Jira API reference

Returns a list of users that match the search string.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\User\UserService;

try {
    $us = new UserService();

    $paramArray = [
        //'username' => null,
        'project' => 'TEST',
        //'issueKey' => 'TEST-1',
        'startAt' => 0,
        'maxResults' => 50, //max 1000
        //'actionDescriptorId' => 1,
    ];

    $users = $us->findAssignableUsers($paramArray);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Find users by query

See Jira API reference

Returns a list of users that match the search string.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\User\UserService;

try {
    $us = new UserService();

    $paramArray = [
      'query' => 'is watcher of TEST',
    ];

    $users = $us->findUsersByQuery($paramArray);
    var_dump($users);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

delete User

See Jira API reference

Removes user.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\User\UserService;

try {
    $us = new UserService();

    $paramArray = ['username' => 'user@example.com'];

    $users = $us->deleteUser($paramArray);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

update User

See Jira API reference

Updates user.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\User\UserService;

try {
    $us = new UserService();

    $paramArray = ['username' => 'user@example.com'];

    // create new user
    $user = [
            'name'=>'charli',
            'password' => 'abracada',
            'emailAddress' => 'charli@atlassian.com',
            'displayName' => 'Charli of Atlassian',
        ];

    $updatedUser = $us->update($paramArray, $user)

    var_dump($updatedUser);

} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Create Group

See Jira API reference

Create new group.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\Group\GroupService;
use JiraRestApi\Group\Group;

try {
    $g = new Group();

    $g->name = 'Test group for REST API';

    $gs = new GroupService();
    $ret = $gs->createGroup($g);

    var_dump($ret);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get Users from group

See Jira API reference

returns a paginated list of users who are members of the specified group and its subgroups.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\Group\GroupService;

try {
   $queryParam = [
        'groupname' => 'Test group for REST API',
        'includeInactiveUsers' => true, // default false
        'startAt' => 0,
        'maxResults' => 50,
    ];

    $gs = new GroupService();

    $ret = $gs->getMembers($queryParam);

    // print all users in the group
    foreach($ret->values as $user) {
        print_r($user);
    }
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Add User to group

See Jira API reference

add user to given group.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\Group\GroupService;

try {
    $groupName  = '한글 그룹 name';
    $userName = 'lesstif';

    $gs = new GroupService();

    $ret = $gs->addUserToGroup($groupName, $userName);

    // print current state of the group.
    print_r($ret);

} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Remove User from group

See Jira API reference

Removes given user from a group.

<?php
require 'vendor/autoload.php';

use JiraRestApi\JiraException;
use JiraRestApi\Group\GroupService;

try {
    $groupName  = '한글 그룹 name';
    $userName = 'lesstif';

    $gs = new GroupService();

    $gs->removeUserFromGroup($groupName, $userName);

} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get All Priority list

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Priority\PriorityService;
use JiraRestApi\JiraException;

try {
    $ps = new PriorityService();

    $p = $ps->getAll();
    
    var_dump($p);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get Priority

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Priority\PriorityService;
use JiraRestApi\JiraException;

try {
    $ps = new PriorityService();

    $p = $ps->get(1);
    
    var_dump($p);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get Attachment Info

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Attachment\AttachmentService;
use JiraRestApi\JiraException;

try {
    $attachmentId = 12345;

    $atts = new AttachmentService();
    $att = $atts->get($attachmentId);

    var_dump($att);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Gets the attachment information and saves the attachment into the outDir directory.

<?php
require 'vendor/autoload.php';

use JiraRestApi\Attachment\AttachmentService;
use JiraRestApi\JiraException;

try {
    $attachmentId = 12345;
    $outDir = 'attachment_dir';
    
    $atts = new AttachmentService();
    $att = $atts->get($attachmentId, $outDir, $overwrite = true);

    var_dump($att);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Remove attachment

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Attachment\AttachmentService;
use JiraRestApi\JiraException;

try {
    $attachmentId = 12345;

    $atts = new AttachmentService();

    $atts->remove($attachmentId);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Create version

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Issue\Version;
use JiraRestApi\Project\ProjectService;
use JiraRestApi\Version\VersionService;
use JiraRestApi\JiraException;

try {
    $projectService = new ProjectService();
    $project = $projectService->get('TEST');

    $versionService = new VersionService();

    $version = new Version();

    $version->setName('1.0.0')
            ->setDescription('Generated by script')
            ->setReleased(true)
            ->setReleaseDate(new \DateTime())
            ->setProjectId($project->id);

    $res = $versionService->create($version);

    var_dump($res);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Update version

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Version\VersionService;
use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $versionService = new VersionService();
    $projectService = new ProjectService();

    $ver = $projectService->getVersion('TEST', '1.0.0');

    // update version
    $ver->setName($ver->name . ' Updated name')
        ->setDescription($ver->description . ' Updated description')
        ->setReleased(false)
        ->setReleaseDate(
            (new \DateTime())->add(date_interval_create_from_date_string('1 months 3 days'))
        );

    $res = $versionService->update($ver);

    var_dump($res);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Delete version

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Version\VersionService;
use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $versionService = new VersionService();
    $projectService = new ProjectService();

    $version = $projectService->getVersion('TEST', '1.0.0');

    $res = $versionService->delete($version);

    var_dump($res);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get version related issues

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Version\VersionService;
use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $versionService = new VersionService();
    $projectService = new ProjectService();

    $version = $projectService->getVersion('TEST', '1.0.0');

    $res = $versionService->getRelatedIssues($version);

    var_dump($res);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get version unresolved issues

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Version\VersionService;
use JiraRestApi\Project\ProjectService;
use JiraRestApi\JiraException;

try {
    $versionService = new VersionService();
    $projectService = new ProjectService();

    $version = $projectService->getVersion('TEST', '1.0.0');

    $res = $versionService->getUnresolvedIssues($version);

    var_dump($res);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Create component

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Component\ComponentService;
use JiraRestApi\Issue\Version;
use JiraRestApi\Project\Component;
use JiraRestApi\JiraException;

try {
    $componentService = new ComponentService();
    
    $component = new Component();
    $component->setName('my component')
              ->setDescription('Generated by script')
              ->setProjectKey('TEST');

    $res = $componentService->create($component);

    var_dump($res);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Update component

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Component\ComponentService;
use JiraRestApi\Issue\Version;
use JiraRestApi\Project\Component;
use JiraRestApi\JiraException;

try {
    $componentService = new ComponentService();
    
    $component = $componentService->get(10000); // component-id
    $component->setName($component->name . ' Updated name')
              ->setDescription($component->description . ' Updated descrption')
              ->setLeadUserName($component->lead->key);  // bug in jira api

    $res = $componentService->update($component);

    var_dump($res);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Delete component

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Component\ComponentService;
use JiraRestApi\Issue\Version;
use JiraRestApi\Project\Component;
use JiraRestApi\JiraException;

try {
    $componentService = new ComponentService();
    
    $component = $componentService->get(10000); // component-id

    $res = $componentService->delete($component);

    var_dump($res);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get board list

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Board\BoardService;

try {
  $board_service = new BoardService();
  $board = $board_service->getBoardList();
  
  var_dump($board);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get board info

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Board\BoardService;

try {
  $board_service = new BoardService();
  $board_id = 1;
  $board = $board_service->getBoard($board_id);
  
  var_dump($board);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get board issues

See Jira API reference

<?php
require 'vendor/autoload.php';

use JiraRestApi\Board\BoardService;

try {
  $board_service = new BoardService();
  $board_id = 1;
  $issues = $board_service->getBoardIssues($board_id, [
    'maxResults' => 500,
    'jql' => urlencode('status != Closed'),
  ]);
  
  foreach ($issues as $issue) {
    var_dump($issue);
  }
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get board epics

See Jira API reference

<?php
require 'vendor/autoload.php';

try {
  $board_service = new JiraRestApi\Board\BoardService();
  $board_id = 1;
  $epics = $board_service->getBoardEpics($board_id, [
    'maxResults' => 500,
  ]);
  
  foreach ($epics as $epic) {
    var_dump($epic);
  }
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get epic info

See Jira API reference

<?php
require 'vendor/autoload.php';

try {
  $epic_service = new JiraRestApi\Epic\EpicService();
  $epic_id = 1;
  $epic = $epic_service->getEpic($epic_id);
  
  var_dump($epic);
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

Get epic issues

See Jira API reference

<?php
require 'vendor/autoload.php';

try {
  $epic_service = new JiraRestApi\Epic\EpicService();
  $epic_id = 1;
  $issues = $epic_service->getEpicIssues($epic_id, [
    'maxResults' => 500,
    'jql' => urlencode('status != Closed'),
  ]);
  
  foreach ($issues as $issue) {
    var_dump($issue);
  }
} catch (JiraRestApi\JiraException $e) {
    print('Error Occured! ' . $e->getMessage());
}

JIRA Rest API Documents

Author: lesstif
Source Code: https://github.com/lesstif/php-jira-rest-client 
License: View license

#php #cakephp #jira #rest #client 

PHP-jira-rest-client: PHP Classes interact Jira with The REST API

BioJava | A Java Framework for Processing Biological Data

BioJava

BioJava is an open-source project dedicated to providing a Java framework for processing biological data. It provides analytical and statistical routines, parsers for common file formats, reference implementations of popular algorithms, and allows the manipulation of sequences and 3D structures. The goal of the biojava project is to facilitate rapid application development for bioinformatics.

Please visit our homepage.

Documentation

The BioJava tutorial is a great place to get started. It is most complete for the biojava-structure module.

The BioJava Cookbook contains an older and slightly outdated collection of simple examples that teach the basics for how to work with BioJava.

Full javadocs are available at the BioJava website.

Maven Repository

BioJava release are available from Maven Central.

Quick Installation

If you are using Maven you can add the BioJava repository by adding the following XML to your project pom.xml file:

    <dependencies>
      <dependency>
        <groupId>org.biojava</groupId>
        <artifactId>biojava-core</artifactId>
        <version>6.0.5</version>
      </dependency>
      <!-- other biojava modules as needed -->
    </dependencies>

Mailing Lists

BioJava has one main mailing list. In order to avoid SPAM the list only accepts postings from list members. Anybody can become a list member, so please subscribe before you post. If you send without being subscribed your mail might get stuck in the moderation loop, which can cause several weeks of delay (no fun to read through all that spam).

biojava-l general discussion list

This list is intended for general discussion, advice, questions, offers of help, announcements, expressions of appreciation, bugs found in release code and requests for features.

biojava-dev developers list

A dev mailing list used to exist, but it has now been shut down. For dev discussions we now use github issues. Please search existing issues and if you don't find the answer to your question submit a new issue.

Please cite

BioJava 5: A community driven open-source bioinformatics library
Aleix Lafita, Spencer Bliven, Andreas Prlić, Dmytro Guzenko, Peter W. Rose, Anthony Bradley, Paolo Pavan, Douglas Myers-Turnbull, Yana Valasatava, Michael Heuer, Matt Larson, Stephen K. Burley, Jose M. Duarte 
PLOS Computational Biology 15(2): e1006791 

Download Details:
Author: biojava
Source Code: https://github.com/biojava/biojava
License: LGPL-2.1 license

#java #rest

BioJava | A Java Framework for Processing Biological Data

Crnk Framework | JSON API Library for Java

What is Crnk?

Crnk is an implementation of the JSON API specification and recommendations in Java to facilitate building RESTful applications. It provides many conventions and building blocks that application can benefit from. This includes features such as sorting, filtering, pagination, requesting complex object graphs, sparse field sets, attaching links to data or atomically execute multiple operations. Further integration with frameworks and libraries such as Spring, CDI, JPA, Bean Validation, Dropwizard, Servlet API, Zipkin and and more ensure that JSON API plays well together with the Java ecosystem. Have a look at www.crnk.io and the documentation for more detailed information.

Release notes can be found in http://www.crnk.io/releases/.

Repository

Crnk Maven artifacts are available from jcenter/bintray: https://bintray.com/crnk-project.

Note that due to reliability issues of MavenCentral we only rarely publish there.

Requirements

Crnk requires Java 1.8 or later and an SLF4J setup for logging.

Example

See https://github.com/crnk-project/crnk-example/

Gradle settings.gradle can look like:

gradle.beforeProject { Project project ->
    project.with {
        buildscript {
            repositories {
                jcenter()
                // maven { url 'https://dl.bintray.com/crnk-project/mavenLatest/' }
            }
        }
        repositories {
            jcenter()
            // maven { url 'https://dl.bintray.com/crnk-project/mavenLatest/' }
        }
    }
}

and the build.gradle:

dependencies {
    implementation platform('io.crnk:crnk-bom:INSERT_VERSION_HERE')
    annotationProcessor platform('io.crnk:crnk-bom:INSERT_VERSION_HERE')

    annotationProcessor 'io.crnk:crnk-gen-java'

    implementation "io.crnk:crnk-setup-spring-boot2"
    implementation "io.crnk:crnk-data-jpa"
    implementation "io.crnk:crnk-data-facet"
    implementation "io.crnk:crnk-format-plain-json"
    implementation "io.crnk:crnk-validation"
    implementation "io.crnk:crnk-home"
    implementation "io.crnk:crnk-ui"
    implementation "io.crnk:crnk-operations"
    implementation "io.crnk:crnk-security"
}

and a basic Java example:

@JsonApiResource(type = "vote")
@Data
public class Vote {

    @JsonApiId
    private UUID id;

    private int stars;

}

public class VoteRepository extends ResourceRepositoryBase<Vote, UUID> {

    public Map<UUID, Vote> votes = new ConcurrentHashMap<>();

    public VoteRepository() {
        super(Vote.class);
    }

    @Override
    public ResourceList<Vote> findAll(QuerySpec querySpec) {
        return querySpec.apply(votes.values());
    }

    @Override
    public <S extends Vote> S save(S entity) {
        votes.put(entity.getId(), entity);
        return null;
    }

    @Override
    public void delete(UUID id) {
        votes.remove(id);
    }
}

or with JPA:

@JsonApiResource(type = "person")
@Entity
@Data
public class PersonEntity {

    @Id
    private UUID id;

    private String name;

    private int year;

    @OneToMany(mappedBy = "movie")
    private List<RoleEntity> roles = new ArrayList<>();

    @Version
    private Integer version;
}

public class PersonRepository extends JpaEntityRepositoryBase<PersonEntity, UUID> {

    public PersonRepository() {
        super(PersonEntity.class);
    }

    @Override
    public PersonEntity save(PersonEntity entity) {
        // add your save logic here
        return super.save(entity);
    }

    @Override
    public PersonEntity create(PersonEntity entity) {
        // add your create logic here
        return super.create(entity);
    }

    @Override
    public void delete(UUID id) {
        // add your save logic here
        super.delete(id);
    }
}

Crnk integrates well with many frameworks. Have a look at the documentation and carefully choose what you need. Don't hesitate to ask for help and suggest improvements!

Licensing

Crnk is licensed under the Apache License, Version 2.0. You can grab a copy of the license at http://www.apache.org/licenses/LICENSE-2.0.

Building from Source

Crnk make use of Gradle for its build. To build the complete project run

gradlew clean build

Note as part of the build a local Node installation is downloaded to build the frontend parts (crnk-ui) of the project.

Links

Download Details:
Author: crnk-project
Source Code: https://github.com/crnk-project/crnk-framework
License: Apache-2.0 license

#java #rest

Crnk Framework | JSON API Library for Java

Spark | A Simple Expressive Web Framework for Java

Spark

Spark 2.9.4 is out!!

<dependency>
    <groupId>com.sparkjava</groupId>
    <artifactId>spark-core</artifactId>
    <version>2.9.4</version>
</dependency>

Getting started

<dependency>
    <groupId>com.sparkjava</groupId>
    <artifactId>spark-core</artifactId>
    <version>2.9.4</version>
</dependency>
import static spark.Spark.*;

public class HelloWorld {
    public static void main(String[] arg){
        get("/hello", (request, response) -> "Hello World!");
    }
}

View at: http://localhost:4567/hello

Check out and try the examples in the source code. You can also check out the javadoc. After getting the source from github run:

mvn javadoc:javadoc

The result is put in /target/site/apidocs

Examples

Simple example showing some basic functionality

import static spark.Spark.*;

/**
 * A simple example just showing some basic functionality
 */
public class SimpleExample {

    public static void main(String[] args) {

        //  port(5678); <- Uncomment this if you want spark to listen to port 5678 instead of the default 4567

        get("/hello", (request, response) -> "Hello World!");

        post("/hello", (request, response) ->
            "Hello World: " + request.body()
        );

        get("/private", (request, response) -> {
            response.status(401);
            return "Go Away!!!";
        });

        get("/users/:name", (request, response) -> "Selected user: " + request.params(":name"));

        get("/news/:section", (request, response) -> {
            response.type("text/xml");
            return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><news>" + request.params("section") + "</news>";
        });

        get("/protected", (request, response) -> {
            halt(403, "I don't think so!!!");
            return null;
        });

        get("/redirect", (request, response) -> {
            response.redirect("/news/world");
            return null;
        });

        get("/", (request, response) -> "root");
    }
}

A simple CRUD example showing how to create, get, update and delete book resources

import static spark.Spark.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * A simple CRUD example showing how to create, get, update and delete book resources.
 */
public class Books {

    /**
     * Map holding the books
     */
    private static Map<String, Book> books = new HashMap<String, Book>();

    public static void main(String[] args) {
        final Random random = new Random();

        // Creates a new book resource, will return the ID to the created resource
        // author and title are sent in the post body as x-www-urlencoded values e.g. author=Foo&title=Bar
        // you get them by using request.queryParams("valuename")
        post("/books", (request, response) -> {
            String author = request.queryParams("author");
            String title = request.queryParams("title");
            Book book = new Book(author, title);

            int id = random.nextInt(Integer.MAX_VALUE);
            books.put(String.valueOf(id), book);

            response.status(201); // 201 Created
            return id;
        });

        // Gets the book resource for the provided id
        get("/books/:id", (request, response) -> {
            Book book = books.get(request.params(":id"));
            if (book != null) {
                return "Title: " + book.getTitle() + ", Author: " + book.getAuthor();
            } else {
                response.status(404); // 404 Not found
                return "Book not found";
            }
        });

        // Updates the book resource for the provided id with new information
        // author and title are sent in the request body as x-www-urlencoded values e.g. author=Foo&title=Bar
        // you get them by using request.queryParams("valuename")
        put("/books/:id", (request, response) -> {
            String id = request.params(":id");
            Book book = books.get(id);
            if (book != null) {
                String newAuthor = request.queryParams("author");
                String newTitle = request.queryParams("title");
                if (newAuthor != null) {
                    book.setAuthor(newAuthor);
                }
                if (newTitle != null) {
                    book.setTitle(newTitle);
                }
                return "Book with id '" + id + "' updated";
            } else {
                response.status(404); // 404 Not found
                return "Book not found";
            }
        });

        // Deletes the book resource for the provided id
        delete("/books/:id", (request, response) -> {
            String id = request.params(":id");
            Book book = books.remove(id);
            if (book != null) {
                return "Book with id '" + id + "' deleted";
            } else {
                response.status(404); // 404 Not found
                return "Book not found";
            }
        });

        // Gets all available book resources (ids)
        get("/books", (request, response) -> {
            String ids = "";
            for (String id : books.keySet()) {
                ids += id + " ";
            }
            return ids;
        });
    }

    public static class Book {

        public String author, title;

        public Book(String author, String title) {
            this.author = author;
            this.title = title;
        }

        public String getAuthor() {
            return author;
        }

        public void setAuthor(String author) {
            this.author = author;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }
    }
}

Example showing a very simple (and stupid) authentication filter that is executed before all other resources

import static spark.Spark.*;

import java.util.HashMap;
import java.util.Map;

/**
 * Example showing a very simple (and stupid) authentication filter that is
 * executed before all other resources.
 *
 * When requesting the resource with e.g.
 *     http://localhost:4567/hello?user=some&password=guy
 * the filter will stop the execution and the client will get a 401 UNAUTHORIZED with the content 'You are not welcome here'
 *
 * When requesting the resource with e.g.
 *     http://localhost:4567/hello?user=foo&password=bar
 * the filter will accept the request and the request will continue to the /hello route.
 *
 * Note: There is a second "before filter" that adds a header to the response
 * Note: There is also an "after filter" that adds a header to the response
 */
public class FilterExample {

    private static Map<String, String> usernamePasswords = new HashMap<String, String>();

    public static void main(String[] args) {

        usernamePasswords.put("foo", "bar");
        usernamePasswords.put("admin", "admin");

        before((request, response) -> {
            String user = request.queryParams("user");
            String password = request.queryParams("password");

            String dbPassword = usernamePasswords.get(user);
            if (!(password != null && password.equals(dbPassword))) {
                halt(401, "You are not welcome here!!!");
            }
        });

        before("/hello", (request, response) -> response.header("Foo", "Set by second before filter"));

        get("/hello", (request, response) -> "Hello World!");

        after("/hello", (request, response) -> response.header("spark", "added by after-filter"));

        afterAfter("/hello", (request, response) -> response.header("finally", "executed even if exception is throw"));

        afterAfter((request, response) -> response.header("finally", "executed after any route even if exception is throw"));
    }
}

Example showing how to use attributes

import static spark.Spark.after;
import static spark.Spark.get;

/**
 * Example showing the use of attributes
 */
public class FilterExampleAttributes {

    public static void main(String[] args) {
        get("/hi", (request, response) -> {
            request.attribute("foo", "bar");
            return null;
        });

        after("/hi", (request, response) -> {
            for (String attr : request.attributes()) {
                System.out.println("attr: " + attr);
            }
        });

        after("/hi", (request, response) -> {
            Object foo = request.attribute("foo");
            response.body(asXml("foo", foo));
        });
    }

    private static String asXml(String name, Object value) {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><" + name +">" + value + "</"+ name + ">";
    }
}

Example showing how to serve static resources

import static spark.Spark.*;

public class StaticResources {

    public static void main(String[] args) {

        // Will serve all static file are under "/public" in classpath if the route isn't consumed by others routes.
        // When using Maven, the "/public" folder is assumed to be in "/main/resources"
        staticFileLocation("/public");

        get("/hello", (request, response) -> "Hello World!");
    }
}

Example showing how to define content depending on accept type

import static spark.Spark.*;

public class JsonAcceptTypeExample {

    public static void main(String args[]) {

        //Running curl -i -H "Accept: application/json" http://localhost:4567/hello json message is read.
        //Running curl -i -H "Accept: text/html" http://localhost:4567/hello HTTP 404 error is thrown.
        get("/hello", "application/json", (request, response) -> "{\"message\": \"Hello World\"}");
    }
} 

Example showing how to render a view from a template. Note that we are using ModelAndView class for setting the object and name/location of template.

First of all we define a class which handles and renders output depending on template engine used. In this case FreeMarker.

public class FreeMarkerTemplateEngine extends TemplateEngine {

    private Configuration configuration;

    protected FreeMarkerTemplateEngine() {
        this.configuration = createFreemarkerConfiguration();
    }

    @Override
    public String render(ModelAndView modelAndView) {
        try {
            StringWriter stringWriter = new StringWriter();

            Template template = configuration.getTemplate(modelAndView.getViewName());
            template.process(modelAndView.getModel(), stringWriter);

            return stringWriter.toString();
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        } catch (TemplateException e) {
            throw new IllegalArgumentException(e);
        }
    }

    private Configuration createFreemarkerConfiguration() {
        Configuration retVal = new Configuration();
        retVal.setClassForTemplateLoading(FreeMarkerTemplateEngine.class, "freemarker");
        return retVal;
    }
}

Then we can use it to generate our content. Note how we are setting model data and view name. Because we are using FreeMarker, in this case a Map and the name of the template is required:

public class FreeMarkerExample {

    public static void main(String args[]) {

        get("/hello", (request, response) -> {
            Map<String, Object> attributes = new HashMap<>();
            attributes.put("message", "Hello FreeMarker World");

            // The hello.ftl file is located in directory:
            // src/test/resources/spark/examples/templateview/freemarker
            return modelAndView(attributes, "hello.ftl");
        }, new FreeMarkerTemplateEngine());
    }
}

Example of using Transformer.

First of all we define the transformer class, in this case a class which transforms an object to JSON format using gson API.

public class JsonTransformer implements ResponseTransformer {

    private Gson gson = new Gson();

    @Override
    public String render(Object model) {
        return gson.toJson(model);
    }
}

And then the code which return a simple POJO to be transformed to JSON:

public class TransformerExample {

    public static void main(String args[]) {
        get("/hello", "application/json", (request, response) -> {
            return new MyMessage("Hello World");
        }, new JsonTransformer());
    }
}

Debugging

See Spark-debug-tools as a separate module.

Learn More

Sponsor the project here https://github.com/sponsors/perwendel

For documentation please go to: http://sparkjava.com/documentation

For usage questions, please use stack overflow with the “spark-java” tag

Javadoc: http://javadoc.io/doc/com.sparkjava/spark-core

When committing to the project please use Spark format configured in https://github.com/perwendel/spark/blob/master/config/spark_formatter_intellij.xml

Download Details:
Author: perwendel
Source Code: https://github.com/perwendel/spark
License: Apache-2.0 license

#java #rest

Spark | A Simple Expressive Web Framework for Java

Restlet Framework | Leading REST API framework for Java

Restlet Framework

The leading RESTful Web API framework for Java

Thanks to Restlet Framework's powerful routing and filtering capabilities, unified client and server Java API, developers can build secure and scalable RESTful web APIs.

It is available in editions for all major platforms (Java SE/EE, Google App Engine, OSGi, GWT, Android) and offers numerous extensions to fit the needs of all developers.

It is available under the terms of either the Apache Software License 2.0 or the Eclipse Public License 1.0.

http://restlet.talend.com

Learn more

To learn more about Restlet Framework, please have a look at the following resources:

Download Details:
Author: restlet
Source Code: https://github.com/restlet/restlet-framework-java
License:

#java #rest

Restlet Framework | Leading REST API framework for Java

Rest Express | Java Framework to Quickly Create RESTful Microservices

RestExpress

RestExpress is a thin wrapper on the JBOSS Netty HTTP stack to provide a simple and easy way to create RESTful services in Java that support massive Internet Scale and performance.

Born to be simple, only three things are required to wire up a service:

  1. The main class which utilizes the RestExpress DSL to create a server instance.
  2. Use a DSL for the declaration of supported URLs and HTTP methods of the service(s) (much like routes.rb in a Rails app).
  3. Service implementation(s), which is/are a simple POJO--no interface or super class implementation.

See: https://github.com/RestExpress/RestExpress-Archetype to get started (there is a README there).

Maven Usage

Stable:

        <dependency>
            <groupId>com.strategicgains</groupId>
            <artifactId>RestExpress</artifactId>
            <version>0.11.3</version>
        </dependency>

Development:

        <dependency>
            <groupId>com.strategicgains</groupId>
            <artifactId>RestExpress</artifactId>
            <version>0.12.0-SNAPSHOT</version>
        </dependency>

Or download the jar directly from: http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22RestExpress%22

Note that to use the SNAPSHOT version, you must enable snapshots and a repository in your pom file as follows (if you already have a profile with repositories in your pom, you can just copy the section):

  <profiles>
    <profile>
       <id>allow-snapshots</id>
          <activation><activeByDefault>true</activeByDefault></activation>
       <repositories>
         <repository>
           <id>sonatype-snapshots-repo</id>
           <url>https://oss.sonatype.org/content/repositories/snapshots</url>
           <releases><enabled>false</enabled></releases>
           <snapshots><enabled>true</enabled></snapshots>
         </repository>
       </repositories>
     </profile>
  </profiles>

A quick tutorial:

Please see the Maven Archetypes at https://github.com/RestExpress/RestExpress-Archetype for kick-starting your API.

HTTP Methods, if not changed in the fluent (DSL) interface, map to the following:

  • GET --> read(Request, Response)
  • PUT --> update(Request, Response)
  • POST --> create(Request, Response)
  • DELETE --> delete(Request, Response)

You can choose to return objects from the methods, if desired, which will be returned to the client in the body of the response. The object will be marshaled into JSON or XML, depending on the default or based on the format in the request (e.g. '.xml' or '?format=xml').

If you choose to not return a value from the method (void methods) and using raw responses, then call response.setResponseNoContent() before returning to set the response HTTP status code to 204 (no content).

On successful creation, call response.setResponseCreated() to set the returning HTTP status code to 201.

For more real-world examples, see the https://github.com/RestExpress/RestExpress-Examples repo which contains additional projects that setup RestExpress services. Simply do 'mvn exec:java' to run them. Then to see what's available perform a GET on the route: '/routes/metadata' to get a list of all the routes (or endpoints) available (e.g. localhost:8081/routes/metadata in the browser).

Change History/Release Notes:

Release 0.12.0-SNAPSHOT (in master)

  • BREAKING CHANGE Added Request.getScheme() which makes a guess re. http/https from host. Altered Request.getUrl() to use it instead of getProtocol().
  • BREAKING CHANGE Removed XML support. No more XStream!
  • BREAKING CHANGE Issue#140 - Use io.netty.handler.ssl.SslContext instead of java.net.ssl.SSLContext. Using SslUtil still works. Otherwise, use SslContextBuilder.
  • BREAKING CHANGE Issue#135 - Query-string parameter keys and values are URL decoded before putting them into the header and query-string maps.
  • BREAKING CHANGE Changed getBaseUrl() to consider X-Forwarded-Host, Forwarded and HOST headers to enable constructing baseURL behind load balancers and proxies.
  • Issue #126 - Introduced RestExpress.noCompression() to turn off response GZip and deflate encoding support (the Netty HttpContentCompressor is not put in the pipeline) for speed optimization (e.g. for small payloads).
  • Issue #130 - Throw on BindException.
  • Issues #137-138 - Use char[] vs String in SslUtil (Merge request from albahrani).
  • Issue #131 - RestExpress.setReadTimeoutSeconds(int seconds) or setReadTimeout(long, TimeUnit) now causes RestExpress to inject a ReadTimeoutHandler into the pipeline.
  • Issues #145, 22 - Support for large, multi-part file uploads via POST. Off by default. Call restExpressInstance.supportFileUploads(true) to enable.
  • Upgraded Jackson Databind to 2.12.1
  • Upgraded Netty to 4.1.58.Final
  • Upgraded Xstream to 1.4.11.1
  • Upgraded OWASP encoder to 1.2.3
  • Updated to compile with JDK 11 (use Base64 instead of DatatypeConverter in HttpBasicAuthenticationPreprocessor).
  • Added Environment.loadFromEnvVars(Properties) which loads System.getenv() to override the properties file settings. Values are available in fillValues(Properties).
  • Added JSON serialization and deserialization support for LocalDate as properties.
  • Added Request.getXForwardedHost() and getForwarded() as well as a Forwarded class to enable parsing the parameters in the Forwarded header.

Release 0.11.3 - 10 Feb 2016

  • Changed signature of RestExpress.serializationProvider(SerializationProvider) to return the RestExpress server instance to facilitate method chaining.
  • Issue #38 - Produce error on no routes defined for server. Now throws NoRoutesDefinedException on bind() if no routes are defined.
  • Issue #122 - Listen on prescribed local IP address.
  • Issue #123 - Routes Exception when using PATCH method.
  • Issue #125 - Changed DefaultExceptionMapper to map cause for RuntimeException (not sub-classes) so all exceptions get mapped correctly from the mapping settings. Left a version, LegacyExceptionMapper, for old behavior. If you want legacy behavior, simply call server.setExceptionMapping(new LegacyExceptionMapper());
  • Upgraded to jackson-databind 2.6.0 (from 2.4.2).
  • Minor fixes due to FindBugs report. There were 11 easily fixable issues reported by FindBugs: primarily equals(), hashCode() and compareTo() implementations.
  • Added UnprocessableEntityException (for 422 status code).
  • Upgraded to: Netty 4.0.34.Final, XStream 1.4.8, OWasp Encoder 1.2, Jackson Databind 2.7.1-1, HTTP Client (test) 4.5.1

Release 0.11.2 - 26 Jul 2015

  • Reduced Java source and target to 1.7

Release 0.11.1 - 24 Jul 2015

  • Fixed issue #110 - too many files open error.
  • Fixed issue #108 - request.getRemoteAddress() always returns null.
  • Fixed issue #94 - Load configuration properties from classpath (if available) and override with those loaded from file system.
  • Fixed issue #106 - Deprecated RestExpress.setSerializationProvider() and RestExpress.getSerializationProvider() in favor of RestExpress.setDefaultSerializationProvider() and RestExpress.getDefaultSerializationProvider(). Also added new instance methods serializationProvider() to set and get the instance's serialization provider.
  • Fixed issue #89 - Made RouteBuilder.determineActionMethod() protected so subclasses can override.
  • Enhanced to use EPoll if it's available on the underlying OS.
  • Added support for ':in:' operator on query-string in filter operations (e.g. ?filter=name:in:a,b,c).
  • Introduced RestExpress.shutdown(boolean) to enable tests to optionally wait for a complete shutdown.
  • Fix for returning 416 when offset is requested for an empty resultset (from Chamal Nanayakkara).
  • Updated Java destination to 1.8

Release 0.11.0 - 12 Mar 2015

  • Bug Fix Strip the response body from HEAD requests to conform to RFC 2616(HTTP spec). (from Codey Whitt)
  • Upgraded Netty version from 3.9.5.Final to 4.0.25.Final (from Thomas Colwell and Mathew Leigh).
  • Enhanced QueryRange.asContentRange() and Response.setCollectionResponse() to support output of '*' as max items in range header for count < 0.
  • Added Unit Tests to test RestExpress' ability to compress responses and decompress requests.
  • Added Environment.load(String[], Class) to eliminate the need for Main.loadEnvironments() in all archetype projects.
  • KNOWN ISSUE - Controllers cannot return ReferenceCounted objects that also exist in the Request object. This will cause the transaction to fail with an IllegalReferenceCountException. If a ReferenceCounted object needs to be returned, a separate copy of the object will need to be made (some classes, such as ByteBuf, have a .copy() method to facilitate this).

Release 0.10.6-SNAPSHOT - in '0.10.6' branch

  • Bug Fix Strip the response body from HEAD requests to conform to RFC 2616(HTTP spec). (from Codey Whitt)

Release 0.10.5 - 2 Dec 2014

  • Changed FilterComponent.setValue(String) signature to setValue(Object).
  • Added suite of operators (:<:, :<=:, :>:, :>=:, :=:, :!=:, :*: [as starts-with]) to filter operations.
  • Added UnsupportedMediaTypeException which returns 415 HTTP status code.
  • Added ContentType.SIREN
  • Added RoutePlugin.flags() and .parameters() to retrieve flags and parameters, respectively.
  • Altered httpRequest.xxxHeader() calls to use httpRequest.headers().xxx() to eliminate deprecation warnings.
  • Upgraded to Netty 3.9.5 final.

Release 0.10.4 - 5 Sep 2014

  • Upgraded Jackson Databind to version 2.4.1 to fix an issue with incorrect serialization for objects that have embedded objects.
  • Added XSS prevention outbound encoding to GsonJsonProcessor adding GsonEncodingStringSerializer.
  • Added parseFrom(Request, String[]) method to QueryFilters and QueryOrders.
  • Added tests to verify output media type.
  • Added tests for wrapping of exceptions in processing and preprocessors.
  • Fixed bug in AbstractSerializationProvider.resolveRequest() to use best matched Media Type, if available (vs. just the Content-Type header).
  • Introduced _ignore_http_status query-string parameter to force call to return 200 status (even on failure).
  • Introduced Request.getMediaType() and Request.getSerializationSettings(), as well as, Response.getMediaType().
  • Added new constructor for JacksonJsonProcessor and GsonJsonProcessor to be able to turn off default outbound HTML-encoding behavior.
  • Refactored DefaultRequestHandler.messageReceived() method into two to allow for better instrumentation via java agents like AppDynamics (from Codey Whitt).
  • Upgraded Jackson Databind to 2.4.2
  • Upgraded XStream to 1.4.7
  • Upgraded Netty to 3.9.4.Final
  • Upgraded GSON dependency to 2.3

Release 0.10.3 - 27 May 2014

  • Change URL Pattern matcher to allow URLs with '?' at the end, but no query-string parameters following it.
  • Changed compiler output version to 1.7 (from 1.6).
  • Added SSL support (from Clark Hobbie).
  • Fixed error message in QueryRange.setLimit(int) from 'limit must be >= 0' to 'limit must be > 0' (from GCL).
  • Changed Request.getQueryStringMap() to never return null (from Kevin Williams).
  • Expose the creation of the DefaultRequestHandler (via RestExpress.buildRequestHandler()) to rest engines not using "main()" or "bind()" (from Ryan Dietrich).
  • Fixed issue with QueryOrders.enforceAllowedProperties() threw erroneous exceptions.
  • Added JacksonEncodingStringSerializer (including it in JacksonJsonProcessor) to outbound HTML Entity encode for possible XSS attacks.
  • Updated to Jackson-Databind 2.3.3 (from 2.1.4).

Release 0.10.2 - 3 Apr 2014

  • Refactored ExceptionMapping into an interface, extracting previous implementation into DefaultExceptionMapper.
  • Added new convenience methods on Request: getBodyAsStream(), getBodyAsBytes(), getBodyAsByteBuffer().
  • Added new method Request.getNamedPath() that returns only the route path pattern instead of the entire URL, which Request.getNamedUrl() does.
  • Request.getProtocol() now returns the protocol from the underlying HttpRequest instance.
  • Fixed issue in QueryOrders where only a single valid order parameters is supported. Caused IndexOutOfBoundsException.
  • Fixed an issue in RestExpress.java where finally processors were assigned as post processors. This could affect some finally processor implementations, such as timers, etc. since they are now run later in the process.
  • Fixed an issue with finally processors, they are now called before the message is returned to the client (after serialization), or in the finally block (if an error occurs, response is in indeterminite state).
  • Introduced another factory method in ErrorResult, allowing elimination of exceptionType in output.
  • Introduced RequestContext, a Map of name/value pairs much like the Log4j mapped diagnostic contexts (MDC), as an instrument for passing augmentation data from different sources to lower levels in the framework.
  • Can now add supported MediaRange(s) dynamically at startup, such as application/hal+json.
  • ** Breaking Change ** Repackaged org.serialization.xml to org.restexpress.serialization.xml
  • Added ContentType.HAL_JSON and ContentType.HAL_XML plus new RestExpressServerTest tests.
  • Introduced RoutePlugin to simplify plugins that create internal routes.

Release 0.10.1 - 24 Jan 2014

  • Fixed NPE issue when RestExpress.setSerializationProvider() is not called.
  • Fixed misspelling in JsonSerializationProcessor.java for SUPPORTED_MEDIA_TYPES.
  • Enhanced QueryFilters and QueryOrders to support enforcement of appropriate filter/order properties—enabling the verification of appropriate orders and filters. Throws BadRequestException on failure.
  • Removed core StringUtils in favor of common StringUtils.
  • Fixed issue where default serialization processor was not used if setSerializationProcessor() was not called.
  • Changed RestExpress server startup message from “Starting Server on port ” to “ server listening on port ”

Release 0.10.0 - 3 Jan 2014

  • ** Breaking Change ** Repackaged to 'org.restexpress...' from 'com.strategicgains.restexpress...'
  • ** Breaking Change ** Re-added GSON capability from version 0.8.2, making things a little more pluggable with RestExpress.setSerializationProvider(SerializationProvider). DefaultSerializationProvider is the default. GsonSerializationProvider is also available, but requires adding GSON to your pom file. Must refactor your own custom ResponseProcessor class into a SerializationProvider implementor.
  • ** Breaking Change ** Removed RestExpress.putResponseProcessor(), .supportJson(), .supportXml(), .supportTxt(), .noJson(), .noXml(), noTxt() as this is all implemented in the SerializationProvider.
  • Implemented content-type negotiation using Content-Type header for serialization (e.g. Request.getBodyAs(type)) and Accept header for deserialization. Implementation still favors .{format}, but uses content-type negotiation if format not supplied.
  • Added RestExpress.enforceHttpSpec() and .setEnforceHttpSpec(boolean) to enable setting the HTTP specification enforcement. Previously, enforcement was always turned on. Now default is OFF. With it off, RestExpress allows you to create non-standard (per the HTTP specification) responses.
  • Removed com.strategicgains.restexpress.common.util.Callback interface since it wasn't being used.
  • Upgraded Netty to 3.9.0 Final

Release 0.9.4.2 - 16 Oct 2013

  • Added ErrorResultWrapper and ErrorResult to facilitate only wrapping error responses vs. not wrapping or JSEND-style always-wrapped responses.

Release 0.9.4 - 17 Jul 2013

  • Fixed issue for plugins that are dependent on RouteMetadata. Fixed issue with routes that depend on GET, PUT, POST, DELETE as the default--wasn't generating metadata correctly for that corner case.
  • Fixed issue with RouteBuilder metadata generation where it wouldn't include the defaults if none set on route.
  • Updated javadoc for getFullPattern() and getPattern().
  • Changed Route.getBaseUrl() to perform null check to avoid getting 'null' string in value.
  • Combined RestExpress-Common as a sub-module and moved core RestExpress functionality to the 'core' sub-module.

Release 0.9.3 - 14 Jun 2013

  • Fixed issue with setter getting called in deserialization instead of Jackson deserializer.
  • Removed LogLevel enumeration due to lack of use.
  • Fixed issue #61 - Large Chunked Request Causes Errors. Added HttpChunkAggregator to the pipeline.
  • Added RestExpress.setMaxContentSize(int) to allow limiting of total content length of requests even if chunked. Default max content size is 25K.
  • Added RestExpress.iterateRouteBuilders(Callback callback) to facilitate plugins, etc. augmenting or extracting information from the declared routes.

Release 0.9.2 - 27 Mar 2013

  • DEPRECATED: Request.getUrlDecodedHeader() and Request.getRawHeader() in favor of getHeader(). Since all HTTP headers and query-string parameters are URL decoded before being put on the Request object, these methods are redundant and cause problems. Their functionality was also changed to simply call getHeader()--so no URL decoding is done in getUrlDecodedHeader().
  • Ensured that parameters extracted from the URL are decoded before setting them as headers on the Request. Now all headers are URL decoded before any call to Request.getHeader(String).
  • Added Request.getRemoteAddress(), which returns the remote address of the request originator.
  • Merged pull request (Issue #58) from amitkarmakar13: add List<String> getHeaders(String)
  • Removed '?' and '#' as valid path segment characters in UrlPattern to conform better with IETF RFC 3986, section 3.3-path. Made '{format}' a first-class element for matching URL route patterns (by using '{format}' instead of a regex to match).

Release 0.9.1 - 4 Mar 2013

Release 0.8.2 - 19 Feb 2013

  • Fixed issue in Request.parseQueryString() to URL decode query-string parameters before putting them in the Request header.

Release 0.8.1 - 16 Jan 2013

  • Removed Ant-build artifacts.
  • Extracted Query-related classes into RestExpress-Common.
  • Fixed maven compile plugin to generate Java target and source for 1.6
  • Updated Netty dependency to 3.6.2.Final
  • Removed dependency on HyperExpress.

Release 0.8.0 - 09 Jan 2013

  • Pushed to Maven Central repository.
  • Introduced maven build.
  • Merged pull request #49 - Added method to get all headers from a HttpRequest.
  • Fixed issue #40 (https://github.com/RestExpress/RestExpress/issues/40).
  • Introduced route 'aliases' where there are multiple URLs for a given service.
  • Introduced concept of "finally" processors, which are executed in the finally block of DefaultRequestHandler and all of them are executed even if an exception is thrown within one of them. This enable the CorsHeaderPlugin to set the appropriate header even on not found errors, etc.
  • Changed to support multiple response types with wrapping or not, etc. Now can support wrapped JSON (.wjson) and XML (.wxml) as well as un-wrapped JSON (.json) and XML (.xml) depending on the format specifier.
  • Now throws BadRequestException (400) if the specified format (e.f. .json) isn't supported by the service suite.
  • Now throws MethodNotAllowedException (405) if the requested URL matches a route but not for the requested HTTP method. Sets the HTTP Allow header to a comma-delimited list of accepted methods.
  • Removed StringUtils.parseQueryString() as it was previously deprecated--use QueryStringParser.
  • Introduced String.join() methods (2).
  • Removed JSONP handling, favoring use of CORS instead, introducing CorsHeaderPlugin and corresponding post-processor.
  • Wraps ETAG header in quotes.
  • Renamed QueryRange.stop to QueryRange.limit.
  • Removed need for RouteDefinition class, moving that functionality into the RestExpress builder.
  • Changed example apps to reflect above elimination of RouteDefinition class.

=================================================================================================== Release 0.7.4 - 30 Nov 2012 (branch 'v0.7.4')


  • Patch release to allow period ('.') as a valid character within URL parameters. Note that this now allows a period to be the last character on the URL whether there is a format-specifier parameter declared for that route, or not. Also, if the route supports the format specifier and there is a period in the last parameter of the URL, anything after the last period will be used as the format for the request--which may NOT be what you want. -- /foo/todd.fredrich --> /foo/{p1}.{format} will use 'fredrich' as the format -- /foo/todd.fredrich.json --> /foo/{p1}.{format} will use 'json' as the format -- /foo/todd. --> /foo/{p1}.{format} will contain 'todd.' for the value of p1 -- /foo/todd. --> /foo/{p1} will contain 'todd.' for the value of p1

=================================================================================================== Release 0.7.3 - 12 July 2012 (branch 'v0.7.3')


  • Patch release to fix an issue with i18n. Fixed issue with DefaultJsonProcessor.deserialize(ChannelBuffer, Class) where underlying InputStreamReader was not UTF-8.

================================================================================================== Release 0.7.2 - 14 May 2012


  • Introduced ExecutionHandler with configuration via RestExpress.setExecutorThreadCount(int) to off-load long-running requests from the NIO workers into a separate thread pool.
  • Introduced CacheControlPlugin which leverages CacheHeaderPostprocessor, DateHeaderPostprocessor and EtagHeaderPostprocessor to respond to GET requests.
  • Introduced EtagHeaderPostprocessor which adds ETag header in response to GET requests.
  • Introduced DateHeaderPostprocessor which adds a Date header to responses to GET requests.
  • Introduced CacheHeaderPostprocessor which support Cache-Control and other caching-related response header best-practices by setting Parameters.Cache.MAX_AGE or Flags.Cache.DONT_CACHE on a route.
  • Changed to use QueryStringParser over StringUtils for query-string and QueryStringDecoder for body parsing. This mitigates HashDoS attacks, since the query-string is parsed before a request is accepted.
  • Deprecated StringUtils in favor of using Netty's QueryStringDecoder or RestExpress's QueryStringParser.
  • Refactored so SerializationProcessor.resolve(Request) is only called once at the end of the request cycle (performance enhancement).

=================================================================================================== Release 0.7.1 - 20 Sep 2011


  • Added rootCause to ResultWrapper data area.
  • Exposed the XStream object from DefaultXmlProcessor.
  • Renamed Link to XLink.
  • Renamed LinkUtils to XLinkUtils, adding asXLinks() method that utilizes XLinkFactory callback to create the XLink instances.
  • Changed URL Matching to support additional characters: '[', ']', '&' which more closely follows W3C the specification.
  • Added ability to return query string parameters as a Map from the Request.
  • Introduced Request.getBaseUrl() which returns protocol and host, without URL path information.
  • Introduced query criteria capability: filter, order, range (for pagination).
  • Introduced the concept of Plugins.
  • Refactored the console routes to use the new plugin concept.
  • Updated Netty jars (to 3.2.5).
  • Added ability to set the number of worker threads via call to RestExpress.setWorkerThreadCount() before calling bind().

=================================================================================================== Release 0.7.0


  • Added gzip request/response handling. On by default. Disable it via call to RestExpress.noCompression() and supportCompression().
  • Added chunked message handling. On by default. Chunking settings are managed via RestExpress.noChunkingSupport(), supportChunking(), and setMaxChunkSize(int).

=================================================================================================== Release 0.6.1.1 - 31 Mar 2011


  • Bug fix to patch erroneously writing to already closed channel in DefaultRequestHandler.exceptionCaught().

=================================================================================================== Release 0.6.1 - 30 Mar 2011


  • Stability release.
  • Fixed issue when unable to URL Decode query string parameters or URL.
  • Introduced SerializationResolver that defines a getDefault() method. Implemented SerializationResolver in DefaultSerializationResolver.
  • Changed UrlPattern to match format based on non-whitespace vs. word characters.
  • Refactored Request.getHeader() into getRawHeader(String) and getUrlDecodedHeader(String), along with corresponding getRawHeader(String,String) and getUrlDecodedHeader(String,String).
  • Renamed realMethod property to effectiveHttpMethod, along with appropriate accessor/mutator.
  • Removed Request from Response constructor signature.
  • Added FieldNamingPolicy to DefaultJsonProcessor (using LOWER_CASE_WITH_UNDERSCORES).
  • getUrlDecodedHeader(String) throws BadRequestException if URL decoding fails.

=================================================================================================== Release 0.6.0.2 - 21 Mar 2011


  • Fixed issue with 'connection reset by peer' causing unresponsive behavior.
  • Utilized Netty logging behavior to add logging capabilities to RestExpress.
  • Made socket-level settings externally configurable: tcpNoDelay, KeepAlive, reuseAddress, soLinger, connectTimeoutMillis, receiveBufferSize.
  • Merged in 0.5.6.1 path release changes.
  • Added enforcement of some HTTP 1.1 specification rules: Content-Type, Content-Length and body content for 1xx, 204, 304 are not allow. Now throws HttpSpecificationException if spec. is not honored.
  • Added ability to add 'flags' and 'parameters' to routes, in that, uri().flag("name") on Route makes test request.isFlagged("name") return true. Also, uri().parameter("name", "value") makes request.getParamater("name") return "value". Not returned/marshaled in the response. Useful for setting internal values/flags for preprocessors, controllers, etc.
  • Added .useRawResponse() and .useWrappedResponse() to fluent route DSL. Causes that particular route to wrap the response or not, independent of global response wrap settings.
  • Parameters parsed from the URL and query string arguments are URL decoded before being placed as Request headers.

=================================================================================================== Release 0.6.0.1


  • Issue #7 - Fixed issue with invalid URL requested where serialization always occurred to the default (JSON). Now serializes to the requested format, if applicable.
  • Issue #11 - Feature enhancement for Kickstart. Now utilizes Rails-inspired configuration environements (e.g. dev, testing, prod, etc.).
  • Issue #12 - Parse URL parameter names out of the URL pattern and include them in the route metadata output.

=================================================================================================== Release 0.6.0


  • Routes now defined in descendant of RouteDeclaration.
  • Refactored everything into RestExpress object, using builder pattern for configuration.
  • Implemented RestExpress DSL to declare REST server in main().
  • Added supported formats and default format to RouteBuilder.
  • Added JSEND-style response wrapping (now default). Call RestExpress.useWrappedResponses() to use.
  • Add ability to support raw response return. Call RestExpress.useRawResponses() to use.
  • Implemented /console/routes.{format} route which return metadata about the routes in this service suite. To use, call RestExpress.supportConsoleRoutes().
  • Exceptions occurring now return in the requested format with the message wrapped and using the appropriate mime type (e.g. application/json or application/xml).
  • Kickstart application has complete build with 'dist' target that builds a runnable jar file and 'run' target that will run the services from the command line.
  • Kickstart application now handles JVM shutdown correctly using JVM shutdown hooks. The method RestExpress.awaitShutdown() uses the DefaultShutdownHook class. RestExpress.shutdown() allows programs to use their own shutdown hooks, calling RestExpress.shutdown() upon shudown to release all resource.

=================================================================================================== Release 0.5.6.1 - 11 Mar 2011


  • Patch release to fix issue with HTTP response status of 204 (No Content) and 304 (Not Modified) where they would return a body of an empty string and content length of 2 ('\r\n'). No longer serializes for 204 or 304. Also no longer serializes for null body response unless a JSONP header is passed in on the query string.

=================================================================================================== Release 0.5.6 - 18 Jan 2011


  • Upgraded to Netty 3.2.3 final.
  • Added getProtocol(), getHost(), getPath() to Request
  • Functionality of getUrl() is now getPath() and getUrl() now returns the entire URL string, including protocol, host and port, and path.

=================================================================================================== Release 0.5.5


  • Added regex URL matching with RouteMapping.regex(String) method.
  • Refactored Route into an abstract class, moving previous functionality into ParameterizedRoute.
  • Added KickStart release artifact to get projects going quickly--simply unzip the kickstart file.
  • Added SimpleMessageObserver which performs simple timings and outputs to System.out.

=================================================================================================== Release 0.5.4


  • Added alias() capability to DefaultTxtProcessor to facilitate custom text serialization.
  • Updated kickstart application to illustrate latest features.
  • Minor refactoring of constants and their locations (moved to RestExpress.java).

=================================================================================================== Release 0.5.3


  • Fixed issue with JSON date/timestamp parsing.
  • Fixed issues with XML date/timestamp parsing.
  • Upgraded to GSON 1.6 release.
  • Added correlation ID to Request to facilitate timing, etc. in pipeline.
  • Added alias(String, Class) to DefaultXmlProcessor.
  • By default, alias List and Link in DefaultXmlProcessor.

=================================================================================================== Release 0.5.2


  • Introduced DateJsonProcessor (sibling to DefaultJsonProcessor) which parses dates vs. time points.
  • Refactored ExceptionMapping.getExceptionFor() signature from Exception to Throwable.
  • Introduced MessageObserver, which accepts notifications of onReceived(), onSuccess(), onException(), onComplete() to facilitate logging, auditing, timing, etc.
  • Changed RouteResolver.resolve() to throw NotFoundException instead of BadRequestException for unresolvable URI.

=================================================================================================== Release 0.5.1


  • Enhanced support for mark, unreserved and some reserved characters in URL. Specifically, added $-+*()~:!' and %. Still doesn't parse URLs with '.' within the string itself--because of the support for .{format} URL section.

=================================================================================================== Release 0.5


  • Renamed repository from RestX to RestExpress.
  • Repackaged everything from com.strategicgains.restx... to com.strategicgains.restexpress...
  • Changed DefaultHttpResponseWriter to output resonse headers correctly.
  • Updated javadoc on RouteBuilder to provide some documentation on route DSL.

=================================================================================================== Release 0.4


  • Fixed error in "Connection: keep-alive" processing during normal and error response writing.
  • Can now create route mappings for OPTIONS and HEAD http methods.
  • Added decoding to URL when Request is constructed.
  • Improved pre-processor implementation, including access to resolved route in request.
  • Better null handling here and there to avoid NullPointerException, including serialization resolver.
  • Improved UT coverage.
  • KickStart application builds and is a more accurate template.

=================================================================================================== Release 0.3


  • Added support for "method tunneling" in POST via query string parameter (e.g. _method=PUT or _method=DELETE)
  • Added JSONP support. Use jsonp= in query string.
  • Utilized Builder pattern in DefaultPipelineFactory, which is now PipelineBuilder.
  • Externalized DefaultRequestHandler in PipelineBuilder and now supports pre/post processors (with associated interfaces).

Download Details:
Author: RestExpress
Source Code: https://github.com/RestExpress/RestExpress
License: Apache-2.0 license

#java #rest

Rest Express | Java Framework to Quickly Create RESTful Microservices

Rest.li: Framework for Building Robust, Scalable RESTful Architectures

Rest.li

Rest.li is an open source REST framework for building robust, scalable RESTful architectures using type-safe bindings and asynchronous, non-blocking IO. Rest.li fills a niche for applying RESTful principles at scale with an end-to-end developer workflow for building REST APIs, which promotes clean REST practices, uniform interface design and consistent data modeling.

Features

  • End-to-end framework for building RESTful APIs
  • Approachable APIs for writing non-blocking client and server code using ParSeq
  • Type-safe development using generated data and client bindings
  • JAX-RS inspired annotation driven server side resource development
  • Engineered and battle tested for high scalability and high availability
  • Optional Dynamic Discovery subsystem adds client side load balancing and fault tolerance
  • Backward compatibility checking to ensure all API changes are safe
  • Support for batch operations, partial updates and projections
  • Web UI for browsing and searching a catalog of rest.li APIs.

Website

https://rest.li

Documentation

See our website for full documentation and examples.

Community

Quickstart Guides and Examples

Download Details:
Author: linkedin
Source Code: https://github.com/linkedin/rest.li
License: View license

#java #rest

Rest.li: Framework for Building Robust, Scalable RESTful Architectures

REST Easy | Jakarta RESTful Web Services Deployment

RESTEasy

RESTEasy is a JBoss.org project aimed at providing productivity frameworks for developing client and server RESTful applications and services in Java. It is mainly a Jakarta RESTful Web Services implementation but you'll find some other experimental code in the repository.

Jakarta RESTful Web Services

RESTEasy is a JBoss project that provides various frameworks to help you build RESTful Web Services and RESTful Java applications. It is a portable implementation of the Jakarta RESTful Web Services specification. The Jakarta RESTful Web Services provides a Java API for RESTful Web Services over the HTTP protocol. Please note that the specification is now under the Eclipse EE4J Project. You can read the entire specification at Jakarta RESTful Web Services.

Getting started with RESTEasy

Documentation

To read the documentation you can read it online.

Issues

Issues are kept in JIRA.

Build

Currently RESTEasy can be built with JDK 11+.

If you want to purely build the project without running the tests, you need to pull down a clone of the RESTEasy repository and run:

$ mvn install -Dmaven.test.skip=true

If you want to build the project with testings run, you may need to specify a profile to use, and may need to configure the Wildfly version you want to run the tests with. Here is an example:

$ export SERVER_VERSION=17.0.0.Final
$ mvn -fae -Dserver.version=$SERVER_VERSION install

Contribute

You are most welcome to contribute to RESTEasy!

Read the Contribution guidelines

Download Details:
Author: resteasy
Source Code: https://github.com/resteasy/Resteasy
License: View license

#java #rest

REST Easy | Jakarta RESTful Web Services Deployment