Commandable HTTP

What are and how to use CommandableHttpServices.

Key takeaways

CommandableHttpService Service that receives remote calls via HTTP/REST protocol to execute commands defined in an ICommandable component.
CommandSet Set of commands that can be called via HTTP.
CommandableHttpClient Client used to consume a CommandableHttpService.

Introduction

In this tutorial, you will learn how to create and consume CommandableHttpServices. This type of service is characterized by containing a set of commands that can be called via the HTTP/REST protocol.

In order to explain its functionality, this tutorial begins by explaining the necessary pre-requisites to work with this component. Then, it shows how to create a command set and a service that uses it. To complete the service, it describes how to include it in a ProcessContainer.

Once the service has been constructed, the tutorial shows how to consume it via a CommandableHttpClient and from any other application. Finally, it provides a full version of the service and client and a summary of what was learned.

Creating a CommandableHttpService

To create a CommandableHttpService, we need to import this class, create a command set, and implement our version of the service. Then, we build a process container to manage and run it. The following sections explain how to do this.

Pre-requisites

In order to create a CommandableHttpService, we need to import this component. This can be done with the following code:

import { CommandableHttpService } from "pip-services3-rpc-nodex";
using PipServices3.Rpc.Services;
import (
	srvc "github.com/pip-services3-gox/pip-services3-rpc-gox/services"
)
import 'package:pip_services3_rpc/pip_services3_rpc.dart';

from pip_services3_rpc.services import CommandableHttpService
Not available

Command set

The key aspect of a CommandableHttpService is its dependence on a set of predefined commands. Thus, in our example, we define a command set containing one command named greeting, which is used to create the phrase “Hello {name}” for a given name. The following code shows how to do this.

import { 
    Command, CommandSet, ICommand, 
    ObjectSchema, Parameters, TypeCode 
} from "pip-services3-commons-nodex";


export class FriendsCommandSet extends CommandSet {
    private _controller: HelloFriendController;

    public constructor(controller: HelloFriendController) {
        super();
        this._controller = controller;
        
        this.addCommand(this.makeGreeting());
    }

    private makeGreeting(): ICommand {
        return new Command(
            'greeting',
            new ObjectSchema(true).withRequiredProperty('name', TypeCode.String),
            async (correlationId: string, args: Parameters) => {
                let name = args.getAsString("name");
                let res = this._controller.greeting(name);
                return res;
            }
        );
    }
}

using System;

using PipServices3.Commons.Commands;
using PipServices3.Commons.Run;
using PipServices3.Commons.Validate;

public class FriendsCommandSet : CommandSet
{
    private HelloFriendController _controller;

    public FriendsCommandSet(HelloFriendController  controller)
    {
        _controller = controller;

        AddCommand(MakeGreeting());
    }

    private ICommand MakeGreeting()
    {
        return new Command("greeting", 
                new ObjectSchema().WithRequiredProperty("name", TypeCode.String), 
                async (string correlationgId, Parameters args) =>
                {
                    var name = args.GetAsString("name");
                    var res = _controller.Greeting(name);
                    return res;
                }
            );
    }
}

import (
	"context"

	ccomand "github.com/pip-services3-gox/pip-services3-commons-gox/commands"
	cconv "github.com/pip-services3-gox/pip-services3-commons-gox/convert"
	crun "github.com/pip-services3-gox/pip-services3-commons-gox/run"
	cvalid "github.com/pip-services3-gox/pip-services3-commons-gox/validate"
)

type FriendsCommandSet struct {
	*ccomand.CommandSet
	controller HelloFriendController
}

func NewFriendsCommandSet(controller HelloFriendController) *FriendsCommandSet {
	c := FriendsCommandSet{}
	c.controller = controller
	c.CommandSet = ccomand.NewCommandSet()
	c.AddCommand(c.makeGreeting())
	return &c
}

func (c *FriendsCommandSet) makeGreeting() ccomand.ICommand {
	return ccomand.NewCommand(
		"greeting",
		cvalid.NewObjectSchema().
			WithRequiredProperty("name", cconv.String),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result interface{}, err error) {
			name := args.GetAsString("name")
			return c.controller.Greeting(name), nil
		},
	)
}

import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_rpc/pip_services3_rpc.dart';


class FriendsCommandSet extends CommandSet {
  HelloFriendController _controller;

  FriendsCommandSet(HelloFriendController controller)
      : _controller = controller,
        super() {
    addCommand(_makeGreeting());
  }

  ICommand _makeGreeting() {
    Future<dynamic> Function(String correlationId, Parameters args) action =
        (String? correlationId, Parameters args) async {
      var name = args.getAsString('name');
      return _controller.greeting(name);
    };

    return Command(
        'greeting',
        ObjectSchema(true).withRequiredProperty('name', TypeCode.String),
        action);
  }
}

from pip_services3_commons.commands import Command, CommandSet, ICommand
from pip_services3_commons.run import Parameters
from pip_services3_commons.validate import Schema, ObjectSchema
from pip_services3_commons.convert import TypeCode
from typing import Optional

class FriendsCommandSet(CommandSet):
    _controller: 'HelloFriendController'

    def __init__(self, controller):
        super().__init__()

        self._controller = controller

        self.add_command(self._make_greeting())

    def _make_greeting(self) -> ICommand:
        def handler(correlation_id: Optional[str], args: Parameters):
            name = args.get_as_string("name")
            res = self._controller.greeting(name)
            return res

        return Command(
            "greeting",
            ObjectSchema(True).with_required_property("name", TypeCode.String),
            handler
        )

Not available

CommandableHttpService

Now that we have our command set, we can code our CommandableHttpService. For this, we create a subclass of this component and add the controller as a dependency.

import { Descriptor} from "pip-services3-commons-nodex";
import { CommandableHttpService } from "pip-services3-rpc-nodex";

export class FriendCommandableHttpService extends CommandableHttpService {
    public constructor() {
        super("commandable_hello_friend");

        this._dependencyResolver.put('controller', new Descriptor("hello-friend", "controller", "*", "*", "*"));
    }
}

using PipServices3.Rpc.Services;

public class FriendCommandableHttpService: CommandableHttpService
{
    public FriendCommandableHttpService(): base("commandable_hello_friend")
    {
        _dependencyResolver.Put("controller", new Descriptor("hello-friend", "controller", "*", "*", "*"));
    }
}

import (
	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	srvc "github.com/pip-services3-gox/pip-services3-rpc-gox/services"
)

type FriendCommandableHttpService struct {
	*srvc.CommandableHttpService

	DependencyResolver *cref.DependencyResolver
}

func NewFriendCommandableHttpService() *FriendCommandableHttpService {
	c := &FriendCommandableHttpService{}
	c.CommandableHttpService = srvc.InheritCommandableHttpService(c, "commandable_hello_friend")
	c.DependencyResolver.Put("controller", cref.NewDescriptor("hello-friend", "controller", "*", "*", "*"))
	return c
}

import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_rpc/pip_services3_rpc.dart';

class FriendCommandableHttpService extends CommandableHttpService {
  FriendCommandableHttpService() : super('commandable_hello_friend') {
    dependencyResolver.put('controller', Descriptor('hello-friend', 'controller', '*', '*', '*'));
  }
}

from pip_services3_rpc.services import CommandableHttpService

class FriendCommandableHttpService(CommandableHttpService):

    def __init__(self):
        super().__init__('commandable_hello_friend')
        self._dependency_resolver.put('controller', Descriptor('hello-friend', 'controller', '*', '*', '*'))
Not available

Controller

The next step is to define a controller that contains the definition of our function.

import { CommandSet, ConfigParams, ICommandable, IConfigurable } from "pip-services3-commons-nodex";

export class HelloFriendController implements IConfigurable, ICommandable {

    private _defaultName: string = "World";
    private _commandSet: FriendsCommandSet;

    public constructor() {
        this._defaultName = "Pip User";
    }
    
    public configure(config: ConfigParams): void {
        this._defaultName = config.getAsStringWithDefault('default_name', this._defaultName);
    }
    
    public getCommandSet(): CommandSet {
        if (this._commandSet == null) {
            this._commandSet = new FriendsCommandSet(this);
        }

        return this._commandSet;
    }

    public greeting(name: string): string {
        return `Hello, ${name ?? this._defaultName}`;
    }
}

using PipServices3.Commons.Commands;
using PipServices3.Commons.Config;

public class HelloFriendController : IConfigurable, ICommandable
{
    private string _defaultName = "World";
    private FriendsCommandSet __commandSet;

    public HelloFriendController()
    {
        _defaultName = "Pip User";
    }

    public void Configure(ConfigParams config)
    {
        _defaultName = config.GetAsStringWithDefault("default_name", _defaultName);
    }

    public CommandSet GetCommandSet()
    {
        if (__commandSet == null)
            __commandSet = new FriendsCommandSet(this);

        return __commandSet;
    }

    public string Greeting(string name)
    {
        return $"Hello, {name ?? _defaultName} !";
    } 
}

import (
    ccomand "github.com/pip-services3-gox/pip-services3-commons-gox/commands"
	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
)

type HelloFriendController struct {
	commandSet  *FriendsCommandSet
	defaultName string
}

func NewHelloFriendController() *HelloFriendController {
	c := HelloFriendController{}
	c.defaultName = "World"
	return &c
}

func (c *HelloFriendController) Configure(ctx context.Context,  config *cconf.ConfigParams) {
	// You can read configuration parameters here...
}


func (c *HelloFriendController) GetCommandSet() *ccomand.CommandSet {
	if c.commandSet == nil {
		c.commandSet = NewFriendsCommandSet(*c)
	}
	return &c.commandSet.CommandSet
}

func (c *HelloFriendController) Greeting(name string) string {
	if name != "" {
		return "Hello, " + name + " !"
	}
	return "Hello, " + c.defaultName + " !"
}

import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_rpc/pip_services3_rpc.dart';

class HelloFriendController implements IConfigurable, ICommandable {
  String _defaultName = 'World';
  FriendsCommandSet? _commandSet;

  HelloFriendController() {
    _defaultName = 'Pip User';
  }

  @override
  void configure(ConfigParams config) {
    _defaultName = config.getAsStringWithDefault('default_name', _defaultName);
  }

  @override
  CommandSet getCommandSet() {
    _commandSet ??= FriendsCommandSet(this);

    return _commandSet!;
  }

  String greeting(String? name) {
    return 'Hello, ${name ?? _defaultName}';
  }
}

 from pip_services3_commons.commands import ICommandable
from pip_services3_commons.config import IConfigurable

class HelloFriendController(IConfigurable, ICommandable): 
                                          
    __defaultName = "World"
    __command_set: 'FriendsCommandSet' = None
        
    def __init__(self):
        self.__defaultName = "Pip User"

    def configure(self, config):
        self.__defaultName = config.get_as_string_with_default("default_name", self.__defaultName)
        
    def get_command_set(self) -> CommandSet:
        if self.__command_set is None:
            self.__command_set = FriendsCommandSet(self)

        return self.__command_set

    def greeting(self, name):
        return f"Hello, {name if name else self.__defaultName} !"
Not available

Factory

Now, we create a factory that builds our service and controller. The code below shows how to do this.

import { Factory } from "pip-services3-components-nodex";
import { Descriptor } from "pip-services3-commons-nodex";


export class HelloFriendServiceFactory extends Factory {
    public constructor() {
        super();
        var CommandableHttpServiceDescriptor = new Descriptor("hello-friend", "service", "commandable-http", "*", "1.0"); // View 
        var ControllerDescriptor = new Descriptor("hello-friend", "controller", "default", "*", "1.0"); // Controller

        this.registerAsType(CommandableHttpServiceDescriptor, FriendCommandableHttpService);
        this.registerAsType(ControllerDescriptor, HelloFriendController);
    }
}

using PipServices3.Commons.Refer;
using PipServices3.Components.Build;


public class HelloFriendServiceFactory: Factory
{
    public HelloFriendServiceFactory()
    {
        var CommandableHttpServiceDescriptor = new Descriptor("hello-friend", "service", "commandable-http", "*", "1.0"); // View 
        var ControllerDescriptor = new Descriptor("hello-friend", "controller", "default", "*", "1.0"); // Controller

        RegisterAsType(CommandableHttpServiceDescriptor, typeof(FriendCommandableHttpService));
        RegisterAsType(ControllerDescriptor, typeof(HelloFriendController));
    }
}

import (
	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	cbuild "github.com/pip-services3-gox/pip-services3-components-gox/build"
)


type HelloFriendServiceFactory struct {
	*cbuild.Factory
}

func NewHelloFriendServiceFactory() *HelloFriendServiceFactory {
	c := HelloFriendServiceFactory{}
	c.Factory = cbuild.NewFactory()

	commandableHttpServiceDescriptor := cref.NewDescriptor("hello-friend", "service", "commandable-http", "*", "1.0") // View
	controllerDescriptor := cref.NewDescriptor("hello-friend", "controller", "default", "*", "1.0")                   // Controller

	c.RegisterType(commandableHttpServiceDescriptor, NewFriendCommandableHttpService)
	c.RegisterType(controllerDescriptor, NewHelloFriendController)

	return &c
}

import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_components/pip_services3_components.dart';
import 'package:pip_services3_rpc/pip_services3_rpc.dart';


class HelloFriendServiceFactory extends Factory {
  HelloFriendServiceFactory() : super() {
    var CommandableHttpServiceDescriptor = Descriptor(
        'hello-friend', 'service', 'commandable-http', '*', '1.0'); // View
    var ControllerDescriptor = Descriptor(
        'hello-friend', 'controller', 'default', '*', '1.0'); // Controller

    registerAsType(
        CommandableHttpServiceDescriptor, FriendCommandableHttpService);
    registerAsType(ControllerDescriptor, HelloFriendController);
  }
}

from pip_services3_commons.refer import Descriptor
from pip_services3_components.build import Factory


class HelloFriendServiceFactory(Factory):
    def __init__(self):
        super(HelloFriendServiceFactory, self).__init__()

 
        CommandableHttpServiceDescriptor = Descriptor('hello-friend', 'service', 'commandable-http', '*', '1.0') # View 
        ControllerDescriptor = Descriptor('hello-friend', 'controller', 'default', '*', '1.0')                   # Controller
                                                                                
            

        self.register_as_type(CommandableHttpServiceDescriptor, FriendCommandableHttpService)    # View 
        self.register_as_type(ControllerDescriptor, HelloFriendController)                       # Controller
Not available

Container

After we have our controller and factory, we create a process container to manage our service’s lifecycle.

import { ProcessContainer } from "pip-services3-container-nodex";
import { DefaultRpcFactory } from "pip-services3-rpc-nodex";
import { DefaultSwaggerFactory } from "pip-services3-swagger-nodex";

export class HelloFriendProcess extends ProcessContainer {
    public constructor() {
        super("hello-friend", "HelloFriend microservice");

        this._configPath = "./config.yaml";

        this._factories.add(new HelloFriendServiceFactory());
        this._factories.add(new DefaultRpcFactory());
        this._factories.add(new DefaultSwaggerFactory());
    }
}

using PipServices3.Container;
using PipServices3.Rpc.Build;
using PipServices3.Swagger.Build;

public class HelloFriendProcess: ProcessContainer
{
    public HelloFriendProcess(): base("hello-friend", "HelloFriend microservice")
    {
        _configPath = "../../../config.yaml";

        _factories.Add(new HelloFriendServiceFactory());
        _factories.Add(new DefaultRpcFactory());
        _factories.Add(new DefaultSwaggerFactory());
    }
}

import (
	rbuild "github.com/pip-services3-gox/pip-services3-rpc-gox/build"
	sbuild "github.com/pip-services3-gox/pip-services3-swagger-gox/build"
	cproc "github.com/pip-services3-gox/pip-services3-container-gox/container"
)

type HelloFriendProcess struct {
	*cproc.ProcessContainer
}

func NewHelloFriendProcess() *HelloFriendProcess {
	c := &HelloFriendProcess{
		ProcessContainer: cproc.NewProcessContainer("Hellow", "Hello friend microservice"),
	}

	c.SetConfigPath("./config.yaml")

	c.AddFactory(NewHelloFriendServiceFactory())
	c.AddFactory(rbuild.NewDefaultRpcFactory())
	c.AddFactory(sbuild.NewDefaultSwaggerFactory())
	return c
}

import 'package:pip_services3_container/pip_services3_container.dart';
import 'package:pip_services3_rpc/pip_services3_rpc.dart';
import 'package:pip_services3_swagger/pip_services3_swagger.dart';

class HelloFriendProcess extends ProcessContainer {
  HelloFriendProcess() : super('hello-friend', 'HelloFriend microservice') {
    configPath = './config.yaml';

    factories.add(HelloFriendServiceFactory());
    factories.add(DefaultRpcFactory());
    factories.add(DefaultSwaggerFactory());
  }
}

from pip_services3_container.ProcessContainer import ProcessContainer
from pip_services3_rpc.build import DefaultRpcFactory
from pip_services3_swagger.build.DefaultSwaggerFactory import DefaultSwaggerFactory



class HelloFriendProcess(ProcessContainer):

    def __init__(self):
        super(HelloFriendProcess, self).__init__('hello-friend', 'HelloFriend microservice')
        self._config_path = './config2DComm.yaml'
        self._factories.add(HelloFriendServiceFactory())
        self._factories.add(DefaultRpcFactory())
        self._factories.add(DefaultSwaggerFactory())
Not available

And, then run it via the run() method.

export async function main() { 
    try {
        let proc = new HelloFriendProcess();
        proc.run(process.argv);
    } catch (ex) {
        console.error(ex);
    }
}

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var runner = (new HelloFriendProcess()).RunAsync(args);
            runner.Wait();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex);
        }
    }
}

import (
	"os"
)

func main() {
	proc := NewHelloFriendProcess()
	proc.Run(os.Args)
}

void main(List<String> argument) {
  try {
    var proc = HelloFriendProcess();
    proc.run(argument);
  } catch (ex) {
    print(ex);
  }
}

if __name__ == '__main__':
    runner = HelloFriendProcess()
    print("run")
    try:
        runner.run()
    except Exception as ex:
        print(ex)
Not available

Once our service is running, it is ready to receive requests.

Consuming a CommandableHttpService

There are several ways to consume our service. In this tutorial, we will consider two of them, namely using Pip.Services' CommandableHttpClient class and via code.

Using a CommandableHttpClient

Pip.Services offers the CommandableHttpClient component, which can be used to interact with a CommandableHttpService. In order to use it, we need to import it first.

import { CommandableHttpClient } from "pip-services3-rpc-nodex";
using PipServices3.Rpc.Clients;
import (
    clnt "github.com/pip-services3-gox/pip-services3-rpc-gox/clients"
)
import 'package:pip_services3_rpc/pip_services3_rpc.dart';
from pip_services3_rpc.clients import CommandableHttpClient
Not available

Once imported, we can create our client by extending this class. The following example shows how to do this:

export class MyCommandableHttpClient extends CommandableHttpClient {
    public constructor(baseRoute: string) {
        super(baseRoute);
    }

    public async greeting(correlationId: string): Promise<string> {
        return await this.callCommand<string>("greeting", correlationId, { name: "Peter" });
    }
}

public class MyCommandableHttpClient : CommandableHttpClient
{
    public MyCommandableHttpClient(string baseRoute) : base(baseRoute) { }

    public async Task<string> Greeting(string correlationId)
    {
        return await CallCommandAsync<string>("greeting", correlationId, new { name = "Peter" });
    }
}

import (
	"reflect"

	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	clnt "github.com/pip-services3-gox/pip-services3-rpc-gox/clients"
)

type MyCommandableHttpClient struct {
	*clnt.CommandableHttpClient
}

func NewMyCommandableHttpClient(baseRoute string) *MyCommandableHttpClient {
	c := MyCommandableHttpClient{}
	c.CommandableHttpClient = clnt.NewCommandableHttpClient(baseRoute)
	return &c
}

func (c *MyCommandableHttpClient) Greeting(correlationId string) (result string, err error) {

	params := cdata.NewEmptyStringValueMap()
	params.Put("name", "Peter")

	res, calErr := c.CallCommand(context.Background(), "greeting", correlationId, cdata.NewAnyValueMapFromValue(params.Value()))
	return clnt.HandleHttpResponse[string](res, correlationId)
}

class MyCommandableHttpClient extends CommandableHttpClient {
  MyCommandableHttpClient(String baseRoute) : super(baseRoute);

  Future<String> greeting(String correlationId) async {
    return await callCommand('greeting', correlationId, {'name': 'Peter'});
  }
}

class MyCommandableHttpClient(CommandableHttpClient):
    
    def __init__(self, base_route: str):
        super().__init__(base_route)

    def greeting(self, correlation_id):
        return self.call_command("greeting", correlation_id, {'name': 'Peter'})
Not available

Which, we then instantiate, configure, and connect to our previously defined service.

let client = new MyCommandableHttpClient("commandable_hello_friend");
client.configure(ConfigParams.fromTuples(
    "connection.protocol", "http",
    "connection.host", "localhost",
    "connection.port", 8080
));

await client.open(null);
var client = new MyCommandableHttpClient("commandable_hello_friend");
client.Configure(ConfigParams.FromTuples(
    "connection.protocol", "http",
    "connection.host", "localhost",
    "connection.port", 8080
));

await client.OpenAsync(null);

import (
	"fmt"
	"reflect"
	"time"
    
	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
)

client := NewMyCommandableHttpClient("commandable_hello_friend")
client.Configure(context.Background(), cconf.NewConfigParamsFromTuples(
	"connection.protocol", "http",
	"connection.host", "localhost",
	"connection.port", 8080,
))

client.Open(context.Background(), "")
defer client.Close(context.Background(), "")

var client = MyCommandableHttpClient('commandable_hello_friend');
client.configure(ConfigParams.fromTuples([
  'connection.protocol', 'http',
  'connection.host', 'localhost',
  'connection.port', 8080
]));

await client.open(null);
client = MyCommandableHttpClient("commandable_hello_friend")
client.configure(ConfigParams.from_tuples("connection.protocol", "http",
                                                 "connection.host", "localhost",
                                                 "connection.port", 8080))
client.open(None)
Not available

And then, we request a greeting and get our response.

let data = await client.greeting("123"); // Returns 'Hello, Peter !'
var data = await client.Greeting("123"); // Returns 'Hello, Peter !'
data, _ := client.Greeting(context.Background(), "123") 

fmt.Println(*data) // Returns 'Hello, Peter !'
var data = await client.greeting('123'); // Returns 'Hello, Peter !'
data = client.greeting("123") # Returns 'Hello, Peter !'
Not available

Using code

We can also call our service via code and obtain a similar result. For example:

const restify = require('restify-clients');

export async function main() { 
    let url = 'http://localhost:8080';
    let rest = restify.createJsonClient({ url: url, version: '*' });

    let data = await new Promise<string>((resolve, reject) => {
        rest.post('/commandable_hello_friend/greeting',
            {name: "Peter"},
            (err, req, res, data) => {
                if (err != null) {
                    reject(err);
                    return;
                }
                resolve(data);
            });
    });

    console.log(data);
}
using System;
using System.Text;
using System.Net.Http;
using System.Threading.Tasks;

var content = "{ \"name\": \"Peter\" }";
var result = new StringContent(content, Encoding.UTF8, "application/json");

var client = new HttpClient();
var response = await client.PostAsync("http://localhost:8080/commandable_hello_friend/greeting", result);
var responseString = await response.Content.ReadAsStringAsync();

Console.WriteLine(responseString); // Returns '"Hello, Cosme !"'
import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

postBody, _ := json.Marshal(map[string]string{
	"name": "Peter",
})

responseBody := bytes.NewBuffer(postBody)

resp, _ := http.Post("http://localhost:8080/commandable_hello_friend/greeting", "application/json", responseBody)
defer resp.Body.Close()

body, _ := ioutil.ReadAll(resp.Body)
sb := string(body)
fmt.Println(sb) // Returns '"Hello, Cosme !"'
import 'dart:convert';
import 'package:http/http.dart' as http;

void main(List<String> argument) async {
    var client = http.Client();

    try {
      var response = await client.post(
          Uri.parse('http://localhost:8080/commandable_hello_friend/greeting'),
          body: json.encode({'name': 'Peter'}));
      var res = response.body;
      print(res);
    } finally {
      client.close();
    }
}

import requests
res = requests.post("http://localhost:8080/commandable_hello_friend/greeting", json={"name": "Cosme"})
res.text # Returns '"Hello, Cosme !"'
Not available

Final code

Below is the complete code for the service and client.

Service

import { 
    Command, CommandSet, ConfigParams, Descriptor, ICommand, 
    ICommandable, 
    IConfigurable, 
    ObjectSchema, Parameters, TypeCode 
} from "pip-services3-commons-nodex";

import { Factory } from "pip-services3-components-nodex";
import { ProcessContainer } from "pip-services3-container-nodex";
import { CommandableHttpService, DefaultRpcFactory } from "pip-services3-rpc-nodex";
import { DefaultSwaggerFactory } from "pip-services3-swagger-nodex";


const restify = require('restify-clients');

export async function main() { 
    // Runner
    try {
        let proc = new HelloFriendProcess();
        proc.run(process.argv);
    } catch (ex) {
        console.error(ex);
    }
}

// Command set
export class FriendsCommandSet extends CommandSet {
    private _controller: HelloFriendController;

    public constructor(controller: HelloFriendController) {
        super();
        this._controller = controller;
        
        this.addCommand(this.makeGreeting());
    }

    private makeGreeting(): ICommand {
        return new Command(
            'greeting',
            new ObjectSchema(true).withRequiredProperty('name', TypeCode.String),
            async (correlationId: string, args: Parameters) => {
                let name = args.getAsString("name");
                let res = this._controller.greeting(name);
                return res;
            }
        );
    }
}

// Service
export class FriendCommandableHttpService extends CommandableHttpService {
    public constructor() {
        super("commandable_hello_friend");

        this._dependencyResolver.put('controller', new Descriptor("hello-friend", "controller", "*", "*", "*"));
    }
}

// Controller
export class HelloFriendController implements IConfigurable, ICommandable {

    private _defaultName: string = "World";
    private _commandSet: FriendsCommandSet;

    public constructor() {
        this._defaultName = "Pip User";
    }
    
    public configure(config: ConfigParams): void {
        this._defaultName = config.getAsStringWithDefault('default_name', this._defaultName);
    }
    
    public getCommandSet(): CommandSet {
        if (this._commandSet == null) {
            this._commandSet = new FriendsCommandSet(this);
        }

        return this._commandSet;
    }

    public greeting(name: string): string {
        return `Hello, ${name ?? this._defaultName}`;
    }
}

// Factory
export class HelloFriendServiceFactory extends Factory {
    public constructor() {
        super();
        var CommandableHttpServiceDescriptor = new Descriptor("hello-friend", "service", "commandable-http", "*", "1.0"); // View 
        var ControllerDescriptor = new Descriptor("hello-friend", "controller", "default", "*", "1.0"); // Controller

        this.registerAsType(CommandableHttpServiceDescriptor, FriendCommandableHttpService);
        this.registerAsType(ControllerDescriptor, HelloFriendController);
    }
}

// Container
export class HelloFriendProcess extends ProcessContainer {
    public constructor() {
        super("hello-friend", "HelloFriend microservice");

        this._configPath = "./config.yaml";

        this._factories.add(new HelloFriendServiceFactory());
        this._factories.add(new DefaultRpcFactory());
        this._factories.add(new DefaultSwaggerFactory());
    }
}
using System;
using PipServices3.Commons.Commands;
using PipServices3.Commons.Config;
using PipServices3.Commons.Refer;
using PipServices3.Commons.Run;
using PipServices3.Commons.Validate;
using PipServices3.Components.Build;
using PipServices3.Container;
using PipServices3.Rpc.Build;
using PipServices3.Rpc.Services;
using PipServices3.Swagger.Build;

namespace ExampleApp
{
    class Program
    {
        // Runner
        static void Main(string[] args)
        {
            try
            {
                var runner = (new HelloFriendProcess()).RunAsync(args);
                runner.Wait();
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }
    }

    // Command set
    public class FriendsCommandSet : CommandSet
    {
        private HelloFriendController _controller;

        public FriendsCommandSet(HelloFriendController controller)
        {
            _controller = controller;

            AddCommand(MakeGreeting());
        }

        private ICommand MakeGreeting()
        {
            return new Command("greeting",
                    new ObjectSchema().WithRequiredProperty("name", TypeCode.String),
                    async (string correlationgId, Parameters args) =>
                    {
                        var name = args.GetAsString("name");
                        var res = _controller.Greeting(name);
                        return res;
                    }
                );
        }
    }

    // Controller
    public class HelloFriendController : IConfigurable, ICommandable
    {
        private string _defaultName = "World";
        private FriendsCommandSet _commandSet;

        public HelloFriendController()
        {
            _defaultName = "Pip User";
        }

        public void Configure(ConfigParams config)
        {
            _defaultName = config.GetAsStringWithDefault("default_name", _defaultName);
        }

        public CommandSet GetCommandSet()
        {
            if (_commandSet == null)
                _commandSet = new FriendsCommandSet(this);

            return _commandSet;
        }

        public string Greeting(string name)
        {
            return $"Hello, {name ?? _defaultName} !";
        }
    }

    // Service
    public class FriendCommandableHttpService : CommandableHttpService
    {
        public FriendCommandableHttpService() : base("commandable_hello_friend")
        {
            _dependencyResolver.Put("controller", new Descriptor("hello-friend", "controller", "*", "*", "*"));
        }
    }

    // Factory
    public class HelloFriendServiceFactory : Factory
    {
        public HelloFriendServiceFactory()
        {
            var CommandableHttpServiceDescriptor = new Descriptor("hello-friend", "service", "commandable-http", "*", "1.0"); // View 
            var ControllerDescriptor = new Descriptor("hello-friend", "controller", "default", "*", "1.0"); // Controller

            RegisterAsType(CommandableHttpServiceDescriptor, typeof(FriendCommandableHttpService));
            RegisterAsType(ControllerDescriptor, typeof(HelloFriendController));
        }
    }

    // Container
    public class HelloFriendProcess : ProcessContainer
    {
        public HelloFriendProcess() : base("hello-friend", "HelloFriend microservice")
        {
            _configPath = "../../../config.yaml";

            _factories.Add(new HelloFriendServiceFactory());
            _factories.Add(new DefaultRpcFactory());
            _factories.Add(new DefaultSwaggerFactory());
        }
    }

}
package main

import (
	"context"
	"os"

	ccomand "github.com/pip-services3-gox/pip-services3-commons-gox/commands"
	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	cconv "github.com/pip-services3-gox/pip-services3-commons-gox/convert"
	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	crun "github.com/pip-services3-gox/pip-services3-commons-gox/run"
	cvalid "github.com/pip-services3-gox/pip-services3-commons-gox/validate"
	cbuild "github.com/pip-services3-gox/pip-services3-components-gox/build"
	cproc "github.com/pip-services3-gox/pip-services3-container-gox/container"
	rbuild "github.com/pip-services3-gox/pip-services3-rpc-gox/build"
	srvc "github.com/pip-services3-gox/pip-services3-rpc-gox/services"
	sbuild "github.com/pip-services3-gox/pip-services3-swagger-gox/build"
)

// Runner
func main() {
	proc := NewHelloFriendProcess()
	proc.Run(context.Background(), os.Environ())
}

// Command set
type FriendsCommandSet struct {
	ccomand.CommandSet
	controller HelloFriendController
}

func NewFriendsCommandSet(controller HelloFriendController) *FriendsCommandSet {
	c := FriendsCommandSet{}
	c.controller = controller
	c.CommandSet = *ccomand.NewCommandSet()
	c.AddCommand(c.makeGreeting())
	return &c
}

func (c *FriendsCommandSet) makeGreeting() ccomand.ICommand {
	return ccomand.NewCommand(
		"greeting",
		cvalid.NewObjectSchema().
			WithRequiredProperty("name", cconv.String),
		func(ctx context.Context, correlationId string, args *crun.Parameters) (result interface{}, err error) {
			name := args.GetAsString("name")
			return c.controller.Greeting(name), nil
		},
	)
}

// Service
type FriendCommandableHttpService struct {
	*srvc.CommandableHttpService
}

func NewFriendCommandableHttpService() *FriendCommandableHttpService {
	c := &FriendCommandableHttpService{}
	c.CommandableHttpService = srvc.InheritCommandableHttpService(c, "commandable_hello_friend")
	c.DependencyResolver.Put(context.Background(), "controller", cref.NewDescriptor("hello-friend", "controller", "*", "*", "*"))
	return c
}

// Controller
type HelloFriendController struct {
	commandSet  *FriendsCommandSet
	defaultName string
}

func NewHelloFriendController() *HelloFriendController {
	c := HelloFriendController{}
	c.defaultName = "World"
	return &c
}

func (c *HelloFriendController) Configure(config *cconf.ConfigParams) {
	// You can read configuration parameters here...
}

func (c *HelloFriendController) GetCommandSet() *ccomand.CommandSet {
	if c.commandSet == nil {
		c.commandSet = NewFriendsCommandSet(*c)
	}
	return &c.commandSet.CommandSet
}

func (c *HelloFriendController) Greeting(name string) string {
	if name != "" {
		return "Hello, " + name + " !"
	}
	return "Hello, " + c.defaultName + " !"
}

// Factory
type HelloFriendServiceFactory struct {
	cbuild.Factory
}

func NewHelloFriendServiceFactory() *HelloFriendServiceFactory {
	c := HelloFriendServiceFactory{}
	c.Factory = *cbuild.NewFactory()

	commandableHttpServiceDescriptor := cref.NewDescriptor("hello-friend", "service", "commandable-http", "*", "1.0") // View
	controllerDescriptor := cref.NewDescriptor("hello-friend", "controller", "default", "*", "1.0")                   // Controller

	c.RegisterType(commandableHttpServiceDescriptor, NewFriendCommandableHttpService)
	c.RegisterType(controllerDescriptor, NewHelloFriendController)

	return &c
}

// Container
type HelloFriendProcess struct {
	*cproc.ProcessContainer
}

func NewHelloFriendProcess() *HelloFriendProcess {
	c := &HelloFriendProcess{
		ProcessContainer: cproc.NewProcessContainer("Hellow", "Hello friend microservice"),
	}

	c.SetConfigPath("./config.yaml")

	c.AddFactory(NewHelloFriendServiceFactory())
	c.AddFactory(rbuild.NewDefaultRpcFactory())
	c.AddFactory(sbuild.NewDefaultSwaggerFactory())
	return c
}


import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_components/pip_services3_components.dart';
import 'package:pip_services3_container/pip_services3_container.dart';
import 'package:pip_services3_rpc/pip_services3_rpc.dart';
import 'package:pip_services3_swagger/pip_services3_swagger.dart';

void main(List<String> argument) async {
  // Runner
  try {
    var proc = HelloFriendProcess();
    proc.run(argument);
  } catch (ex) {
    print(ex);
  }
}

// Command set
class FriendsCommandSet extends CommandSet {
  HelloFriendController _controller;

  FriendsCommandSet(HelloFriendController controller)
      : _controller = controller,
        super() {
    addCommand(_makeGreeting());
  }

  ICommand _makeGreeting() {
    Future<dynamic> Function(String correlationId, Parameters args) action =
        (String? correlationId, Parameters args) async {
      var name = args.getAsString('name');
      return _controller.greeting(name);
    };

    return Command(
        'greeting',
        ObjectSchema(true).withRequiredProperty('name', TypeCode.String),
        action);
  }
}

// Service
class FriendCommandableHttpService extends CommandableHttpService {
  FriendCommandableHttpService() : super('commandable_hello_friend') {
    dependencyResolver.put(
        'controller', Descriptor('hello-friend', 'controller', '*', '*', '*'));
  }
}

// Controller
class HelloFriendController implements IConfigurable, ICommandable {
  String _defaultName = 'World';
  FriendsCommandSet? _commandSet;

  HelloFriendController() {
    _defaultName = 'Pip User';
  }

  @override
  void configure(ConfigParams config) {
    _defaultName = config.getAsStringWithDefault('default_name', _defaultName);
  }

  @override
  CommandSet getCommandSet() {
    _commandSet ??= FriendsCommandSet(this);

    return _commandSet!;
  }

  String greeting(String? name) {
    return 'Hello, ${name ?? _defaultName}';
  }
}

// Factory
class HelloFriendServiceFactory extends Factory {
  HelloFriendServiceFactory() : super() {
    var CommandableHttpServiceDescriptor = Descriptor(
        'hello-friend', 'service', 'commandable-http', '*', '1.0'); // View
    var ControllerDescriptor = Descriptor(
        'hello-friend', 'controller', 'default', '*', '1.0'); // Controller

    registerAsType(
        CommandableHttpServiceDescriptor, FriendCommandableHttpService);
    registerAsType(ControllerDescriptor, HelloFriendController);
  }
}

// Container
class HelloFriendProcess extends ProcessContainer {
  HelloFriendProcess() : super('hello-friend', 'HelloFriend microservice') {
    configPath = './config.yaml';

    factories.add(HelloFriendServiceFactory());
    factories.add(DefaultRpcFactory());
    factories.add(DefaultSwaggerFactory());
  }
}

# Command set

from pip_services3_commons.commands import Command, CommandSet, ICommand
from pip_services3_commons.run import Parameters
from pip_services3_commons.validate import Schema, ObjectSchema
from pip_services3_commons.convert import TypeCode
from typing import Optional

class FriendsCommandSet(CommandSet):
    _controller: 'HelloFriendController'

    def __init__(self, controller):
        super().__init__()

        self._controller = controller

        self.add_command(self._make_greeting())

    def _make_greeting(self) -> ICommand:
        def handler(correlation_id: Optional[str], args: Parameters):
            name = args.get_as_string("name")
            res = self._controller.greeting(name)
            return res

        return Command(
            "greeting",
            ObjectSchema(True).with_required_property("name", TypeCode.String),
            handler
        )

# Controller
from pip_services3_commons.commands import ICommandable
from pip_services3_commons.config import IConfigurable

class HelloFriendController(IConfigurable, ICommandable): 
                                          
    __defaultName = "World"
    __command_set: 'FriendsCommandSet' = None
        
    def __init__(self):
        self.__defaultName = "Pip User"

    def configure(self, config):
        self.__defaultName = config.get_as_string_with_default("default_name", self.__defaultName)
        
    def get_command_set(self) -> CommandSet:
        if self.__command_set is None:
            self.__command_set = FriendsCommandSet(self)

        return self.__command_set

    def greeting(self, name):
        return f"Hello, {name if name else self.__defaultName} !"
    
# Service    
from pip_services3_rpc.services import CommandableHttpService

class FriendCommandableHttpService(CommandableHttpService):

    def __init__(self):
        super().__init__('commandable_hello_friend')
        self._dependency_resolver.put('controller', Descriptor('hello-friend', 'controller', '*', '*', '*'))

        
# Factory
from pip_services3_commons.refer import Descriptor
from pip_services3_components.build import Factory

class HelloFriendServiceFactory(Factory):
    def __init__(self):
        super(HelloFriendServiceFactory, self).__init__()

 
        CommandableHttpServiceDescriptor = Descriptor('hello-friend', 'service', 'commandable-http', '*', '1.0') # View 
        ControllerDescriptor = Descriptor('hello-friend', 'controller', 'default', '*', '1.0')                   # Controller
                                                                                
            

        self.register_as_type(CommandableHttpServiceDescriptor, FriendCommandableHttpService)    # View 
        self.register_as_type(ControllerDescriptor, HelloFriendController)                       # Controller

        

from pip_services3_container.ProcessContainer import ProcessContainer
from pip_services3_rpc.build import DefaultRpcFactory
from pip_services3_swagger.build.DefaultSwaggerFactory import DefaultSwaggerFactory


# Container
class HelloFriendProcess(ProcessContainer):

    def __init__(self):
        super(HelloFriendProcess, self).__init__('hello-friend', 'HelloFriend microservice')
        self._config_path = './config.yaml'
        self._factories.add(HelloFriendServiceFactory())
        self._factories.add(DefaultRpcFactory())
        self._factories.add(DefaultSwaggerFactory())
 
 # Runner 
if __name__ == '__main__':
    runner = HelloFriendProcess()
    print("run")
    try:
        runner.run()
    except Exception as ex:
        print(ex)
Not available

Configuration file

---
# Container context
- descriptor: "pip-services:context-info:default:default:1.0"
  name: "hello-friend"
  description: "HelloFriend microservice"
     
# Console logger
- descriptor: "pip-services:logger:console:default:1.0"
  level: "trace"
     
# Performance counter that post values to log
- descriptor: "pip-services:counters:log:default:1.0"
     
# Controller
- descriptor: "hello-friend:controller:default:default:1.0"
  default_name: "Friend"
     
# Shared HTTP Endpoint
- descriptor: "pip-services:endpoint:http:default:1.0"
  connection:
    protocol: http
    host: 0.0.0.0
    port: 8080
     
# Commandable HTTP service
- descriptor: "hello-friend:service:commandable-http:default:1.0"
  swagger:
    enable: true
    auto: true
    route: swagger
    name: Friends Service
    description: Commandable REST API
  
# Heartbeat service
- descriptor: "pip-services:heartbeat-service:http:default:1.0"
     
# Status service
- descriptor: "pip-services:status-service:http:default:1.0"

# Swagger service
- descriptor: "pip-services:swagger-service:http:default:1.0"

Client

import { ConfigParams } from "pip-services3-commons-nodex";
import { CommandableHttpClient } from "pip-services3-rpc-nodex";


export async function main() { 
    
    let client = new MyCommandableHttpClient("commandable_hello_friend");
    client.configure(ConfigParams.fromTuples(
        "connection.protocol", "http",
        "connection.host", "localhost",
        "connection.port", 8080
    ));

    await client.open(null);

    let data = await client.greeting("123"); // Returns 'Hello, Peter !'
    console.log(data);
}

export class MyCommandableHttpClient extends CommandableHttpClient {
    public constructor(baseRoute: string) {
        super(baseRoute);
    }

    public async greeting(correlationId: string): Promise<string> {
        return await this.callCommand<string>("greeting", correlationId, { name: "Peter" });
    }
}

using System.Threading.Tasks;
using PipServices3.Commons.Config;
using PipServices3.Rpc.Clients;

namespace ExampleApp
{
    class Program
    {
        static void Main(string[] args)
        {

            var client = new MyCommandableHttpClient("commandable_hello_friend");
            client.Configure(ConfigParams.FromTuples(
                "connection.protocol", "http",
                "connection.host", "localhost",
                "connection.port", 8080
            ));

            client.OpenAsync(null).Wait();

            var data = client.Greeting("123");  // Returns 'Hello, Peter !'
        }
    }

    public class MyCommandableHttpClient : CommandableHttpClient
    {
        public MyCommandableHttpClient(string baseRoute) : base(baseRoute) { }

        public async Task<string> Greeting(string correlationId)
        {
            return await CallCommandAsync<string>("greeting", correlationId, new { name = "Peter" });
        }
    }
}

import (
	"context"
	"fmt"

	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	cdata "github.com/pip-services3-gox/pip-services3-commons-gox/data"
	clnt "github.com/pip-services3-gox/pip-services3-rpc-gox/clients"
)

func main() {
	client := NewMyCommandableHttpClient("commandable_hello_friend")
	client.Configure(context.Background(), cconf.NewConfigParamsFromTuples(
		"connection.protocol", "http",
		"connection.host", "localhost",
		"connection.port", 8080,
	))
	client.Open(context.Background(), "")
	defer client.Close(context.Background(), "")
	data, _ := client.Greeting(context.Background(), "123") // Returns 'Hello, Peter !'
	fmt.Println(data)
}

type MyCommandableHttpClient struct {
	*clnt.CommandableHttpClient
}

func NewMyCommandableHttpClient(baseRoute string) *MyCommandableHttpClient {
	c := MyCommandableHttpClient{}
	c.CommandableHttpClient = clnt.NewCommandableHttpClient(baseRoute)
	return &c
}

func (c *MyCommandableHttpClient) Greeting(ctx context.Context, correlationId string) (result string, err error) {

	params := cdata.NewEmptyStringValueMap()
	params.Put("name", "Peter")

	res, calErr := c.CallCommand(context.Background(), "greeting", correlationId, cdata.NewAnyValueMapFromValue(params.Value()))
	if calErr != nil {
		return "", calErr
	}

	return clnt.HandleHttpResponse[string](res, correlationId)
}

import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_rpc/pip_services3_rpc.dart';


export async function main() { 
    
    var client = MyCommandableHttpClient('commandable_hello_friend');
    client.configure(ConfigParams.fromTuples([
      'connection.protocol', 'http',
      'connection.host', 'localhost',
      'connection.port', 8080
    ]));

    await client.open(null);

    var data = await client.greeting('123'); // Returns 'Hello, Peter !'
    print(data);
}

class MyCommandableHttpClient extends CommandableHttpClient {
  MyCommandableHttpClient(String baseRoute) : super(baseRoute);

  Future<String> greeting(String correlationId) async {
    return await callCommand('greeting', correlationId, {'name': 'Peter'});
  }
}

from pip_services3_rpc.clients import CommandableHttpClient
from pip_services3_commons.config import ConfigParams

class MyCommandableHttpClient(CommandableHttpClient):
 
    def greeting(self, correlation_id):
        return self.call_command("greeting", None, {'name': 'Peter'})
    
client = MyCommandableHttpClient("commandable_hello_friend")
client.configure(ConfigParams.from_tuples("connection.protocol", "http",
                                                 "connection.host", "localhost",
                                                 "connection.port", 8080))

client.open(None)

data = client.greeting("123")  # Returns 'Hello, Peter !'
Not available

Wrapping up

In this tutorial, we have learned what is and how to create a CommandableHttpService, and how to consume it via a CommandableHttpClient and from any app via code.