Commandable HTTP

What are and how to use CommandableHttpControllers.

Key takeaways

CommandableHttpController Controller 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 CommandableHttpController.

Introduction

In this tutorial, you will learn how to create and consume CommandableHttpControllers. 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 controller that uses it. To complete the task, it describes how to include it in a ProcessContainer.

Once the controller 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 controller and client and a summary of what was learned.

Creating a CommandableHttpController

To create a CommandableHttpController, we need to import this class, create a command set, and implement our version of the controller. 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 CommandableHttpController, we need to import this component. This can be done with the following code:

import { CommandableHttpController} from "pip-services4-http-node";
Not available
import (
	ctrl "github.com/pip-services4/pip-services4-go/pip-services4-http-go/controllers"
)
Not available
from pip_services4_http.controller import CommandableHttpController
Not available

Command set

The key aspect of a CommandableHttpController 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.

export class FriendsCommandSet extends CommandSet {
    private _service: HelloFriendService;

    public constructor(controller: HelloFriendService) {
        super();
        this._service = controller;
        
        this.addCommand(this.makeGreeting());
    }

    private makeGreeting(): ICommand {
        return new Command(
            'greeting',
            new ObjectSchema(true).withRequiredProperty('name', TypeCode.String),
            async (ctx: IContext, args: Parameters) => {
                let name = args.getAsString("name");
                let res = this._service.greeting(name);
                return res;
            }
        );
    }
}
Not available
import (
	"context"

	cconv "github.com/pip-services4/pip-services4-go/pip-services4-commons-go/convert"
	cexec "github.com/pip-services4/pip-services4-go/pip-services4-components-go/exec"
	cvalid "github.com/pip-services4/pip-services4-go/pip-services4-data-go/validate"
	ccomand "github.com/pip-services4/pip-services4-go/pip-services4-rpc-go/commands"
)

type FriendsCommandSet struct {
	*ccomand.CommandSet
	service HelloFriendService
}

func NewFriendsCommandSet(service HelloFriendService) *FriendsCommandSet {
	c := FriendsCommandSet{}
	c.service = service
	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, args *cexec.Parameters) (result interface{}, err error) {
			name := args.GetAsString("name")
			return c.service.Greeting(name), nil
		},
	)
}

Not available
from pip_services4_rpc.commands import Command, CommandSet, ICommand
from pip_services4_components.exec import Parameters
from pip_services4_data.validate import Schema, ObjectSchema
from pip_services4_commons.convert import TypeCode
from typing import Optional

class FriendsCommandSet(CommandSet):
    _service: 'HelloFriend'

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

        self._context = service

        self.add_command(self._make_greeting())

    def _make_greeting(self) -> ICommand:
        def handler(context: Optional[IContext], args: Parameters):
            name = args.get_as_string("name")
            res = self._service.greeting(name)

            return res

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

Not available

CommandableHttpController

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

export class FriendCommandableHttpController extends CommandableHttpController {
    public constructor() {
        super("commandable_hello_friend");

        this._dependencyResolver.put('service', new Descriptor("hello-friend", "service", "*", "*", "*"));
    }
}
Not available
import (
        ctrl "github.com/pip-services4/pip-services4-go/pip-services4-http-go/controllers"
	cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
)

type FriendCommandableHttpController struct {
	*ctrl.CommandableHttpController 
}

func NewFriendCommandableHttpController () *FriendCommandableHttpController  {
	c := &FriendCommandableHttpController {}
	c.CommandableHttpController  = ctrl.InheritCommandableHttpController(c, "commandable_hello_friend")
	c.DependencyResolver.Put(context.Background(), "service", cref.NewDescriptor("hello-friend", "service", "*", "*", "*"))
	return c
}
Not available
from pip_services4_http.controller import CommandableHttpController

class FriendCommandableHttpController(CommandableHttpController):

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

Service

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

export class HelloFriendService implements IConfigurable, ICommand {

    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}`;
    }
}
Not available
import (
        ccomand "github.com/pip-services4/pip-services4-go/pip-services4-rpc-go/commands"
        cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
)

type HelloFriendService struct {
	commandSet  *FriendsCommandSet
	defaultName string
}

func NewHelloFriendService() *HelloFriendService {
	c := HelloFriendService{}
	c.defaultName = "World"
	return &c
}

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


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

func (c *HelloFriendService) Greeting(name string) string {
	if name != "" {
		return "Hello, " + name + " !"
	}
	return "Hello, " + c.defaultName + " !"
}
Not available
from pip_services4_rpc.commands import ICommandable
from pip_services4_components.config import IConfigurable

class HelloFriendService(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.

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

        this.registerAsType(CommandableHttpControllerDescriptor, FriendCommandableHttpController);
        this.registerAsType(ServiceDescriptor, HelloFriendService);
    }
}
Not available
import (
        cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	cbuild "github.com/pip-services4/pip-services4-go/pip-services4-components-go/build"
)

type HelloFriendServiceFactory struct {
	*cbuild.Factory
}

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

	commandableHttpControllerDescriptor := cref.NewDescriptor("hello-friend", "controller", "commandable-http", "*", "1.0") 
	serviceDescriptor := cref.NewDescriptor("hello-friend", "service", "default", "*", "1.0")  

	c.RegisterType(commandableHttpControllerDescriptor, NewFriendCommandableHttpController)
	c.RegisterType(serviceDescriptor, NewHelloFriendService)

	return &c
}
Not available
from pip_services4_components.refer import Descriptor
from pip_services4_components.build import Factory

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

 
        CommandableHttpControllerDescriptor = Descriptor('hello-friend', 'controller', 'commandable-http', '*', '1.0') # Controller 
        ServiceDescriptor = Descriptor('hello-friend', 'service', 'default', '*', '1.0')                   # Service
                                                                                
            

        self.register_as_type(CommandableHttpControllerDescriptor, FriendCommandableHttpController)    # Controller
        self.register_as_type(ServiceDescriptor, HelloFriendService)                       # Service
Not available

Container

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

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 DefaultHttpFactory());
        this._factories.add(new DefaultSwaggerFactory());
    }
}
Not available
import (
        sbuild "github.com/pip-services4/pip-services4-go/pip-services4-swagger-go/build"
        cproc "github.com/pip-services4/pip-services4-go/pip-services4-container-go/container"
        hbuild "github.com/pip-services4/pip-services4-go/pip-services4-http-go/build"
)

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(hbuild.NewDefaultHttpFactory())
	c.AddFactory(sbuild.NewDefaultSwaggerFactory())
	return c
}
Not available
from pip_services4_container.container import ProcessContainer
from pip_services4_http.build import DefaultRpcFactory
from pip_services4_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(HelloFriendControllerFactory())
        self._factories.add(DefaultRpcFactory())
        self._factories.add(DefaultSwaggerFactory())
Not available

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

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

func main() {
	proc := NewHelloFriendProcess()
	proc.Run(os.Args)
}
Not available
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 CommandableHttpController

There are several ways to consume our controller. 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 CommandableHttpController. In order to use it, we need to import it first.

import { CommandableHttpClient } from "pip-services4-http-node";
Not available
import clnt "github.com/pip-services4/pip-services4-go/pip-services4-http-go/clients"
Not available
from pip_services4_http.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(ctx: Context): Promise<string> {
        return await this.callCommand<string>("greeting", ctx, { name: "Peter" });
    }
}
Not available
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", cdata.NewAnyValueMapFromValue(params.Value()))
	if calErr != nil {
		return "", calErr
	}

	return clnt.HandleHttpResponse[string](res, correlationId)
}
Not available
class MyCommandableHttpClient(CommandableHttpClient):
    
    def __init__(self, base_route: str):
        super().__init__(base_route)

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

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

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

await client.open(null);
Not available
import (
        "context"
	"fmt"

	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/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())
Not available
from pip_services4_components.config import ConfigParams

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(ctx); // Returns 'Hello, Peter !'
Not available
data, _ := client.Greeting(context.Background(), "123") // Returns 'Hello, Peter !'
fmt.Println(data)
Not available
data = client.greeting("123") # Returns 'Hello, Peter !'
Not available

Using code

We can also call our controller 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);
}

Not available
import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	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 !"'
}
Not available
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.

Controller

import { TypeCode } from "pip-services4-commons-node";
import { Parameters, IContext, Descriptor, IConfigurable, ConfigParams, Factory } from "pip-services4-components-node";
import { Command, CommandSet, ICommand } from "pip-services4-rpc-node";
import { ObjectSchema } from "pip-services4-data-node";
import { CommandableHttpController, DefaultHttpFactory } from "pip-services4-http-node";
import { ProcessContainer } from "pip-services4-container-node";
import { DefaultSwaggerFactory } from "pip-services4-swagger-node";


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 _service: HelloFriendService;

    public constructor(controller: HelloFriendService) {
        super();
        this._service = controller;
        
        this.addCommand(this.makeGreeting());
    }

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

// Controller
export class FriendCommandableHttpController extends CommandableHttpController {
    public constructor() {
        super("commandable_hello_friend");

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

// Service
export class HelloFriendService implements IConfigurable, ICommand {

    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 CommandableHttpControllerDescriptor = new Descriptor("hello-friend", "controller", "commandable-http", "*", "1.0"); // View 
        var ServiceDescriptor = new Descriptor("hello-friend", "service", "default", "*", "1.0"); // Controller

        this.registerAsType(CommandableHttpControllerDescriptor, FriendCommandableHttpController);
        this.registerAsType(ServiceDescriptor, HelloFriendService);
    }
}

// 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 DefaultHttpFactory());
        this._factories.add(new DefaultSwaggerFactory());
    }
}

Not available
import (
	"context"
	"os"

	cconv "github.com/pip-services4/pip-services4-go/pip-services4-commons-go/convert"
	cbuild "github.com/pip-services4/pip-services4-go/pip-services4-components-go/build"
	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	cexec "github.com/pip-services4/pip-services4-go/pip-services4-components-go/exec"
	cref "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
	cproc "github.com/pip-services4/pip-services4-go/pip-services4-container-go/container"
	cvalid "github.com/pip-services4/pip-services4-go/pip-services4-data-go/validate"
	hbuild "github.com/pip-services4/pip-services4-go/pip-services4-http-go/build"
	ctrl "github.com/pip-services4/pip-services4-go/pip-services4-http-go/controllers"
	ccomand "github.com/pip-services4/pip-services4-go/pip-services4-rpc-go/commands"
	sbuild "github.com/pip-services4/pip-services4-go/pip-services4-swagger-go/build"
)

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

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(hbuild.NewDefaultHttpFactory())
	c.AddFactory(sbuild.NewDefaultSwaggerFactory())
	return c
}

type HelloFriendServiceFactory struct {
	*cbuild.Factory
}

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

	commandableHttpControllerDescriptor := cref.NewDescriptor("hello-friend", "controller", "commandable-http", "*", "1.0")
	serviceDescriptor := cref.NewDescriptor("hello-friend", "service", "default", "*", "1.0")

	c.RegisterType(commandableHttpControllerDescriptor, NewFriendCommandableHttpController)
	c.RegisterType(serviceDescriptor, NewHelloFriendService)

	return &c
}

type HelloFriendService struct {
	commandSet  *FriendsCommandSet
	defaultName string
}

func NewHelloFriendService() *HelloFriendService {
	c := HelloFriendService{}
	c.defaultName = "World"
	return &c
}

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

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

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

type FriendCommandableHttpController struct {
	*ctrl.CommandableHttpController
}

func NewFriendCommandableHttpController() *FriendCommandableHttpController {
	c := &FriendCommandableHttpController{}
	c.CommandableHttpController = ctrl.InheritCommandableHttpController(c, "commandable_hello_friend")
	c.DependencyResolver.Put(context.Background(), "service", cref.NewDescriptor("hello-friend", "service", "*", "*", "*"))
	return c
}

type FriendsCommandSet struct {
	*ccomand.CommandSet
	service HelloFriendService
}

func NewFriendsCommandSet(service HelloFriendService) *FriendsCommandSet {
	c := FriendsCommandSet{}
	c.service = service
	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, args *cexec.Parameters) (result interface{}, err error) {
			name := args.GetAsString("name")
			return c.service.Greeting(name), nil
		},
	)
}
Not available
# Command set

from pip_services4_rpc.commands import Command, CommandSet, ICommand
from pip_services4_components.exec import Parameters
from pip_services4_data.validate import Schema, ObjectSchema
from pip_services4_commons.convert import TypeCode
from pip_services4_rpc.commands import ICommandable
from pip_services4_components.config import IConfigurable
from pip_services4_http.controller import CommandableHttpController
from pip_services4_components.refer import Descriptor
from pip_services4_components.build import Factory
from pip_services4_container.container import ProcessContainer
from pip_services4_http.build import DefaultRpcFactory
from pip_services4_swagger.build.DefaultSwaggerFactory import DefaultSwaggerFactory
from typing import Optional

class FriendsCommandSet(CommandSet):
    _service: 'HelloFriendService'

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

        self._service = service

        self.add_command(self._make_greeting())

    def _make_greeting(self) -> ICommand:
        def handler(context: Optional[IContext], 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
        )

# Service


class HelloFriendService(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} !"
    
# Controller    

class FriendCommandableHttpController(CommandableHttpController):

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

        
# Factory

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

 
        CommandableHttpControllerDescriptor = Descriptor('hello-friend', 'controller', 'commandable-http', '*', '1.0') # Controller
        ServiceDescriptor = Descriptor('hello-friend', 'service', 'default', '*', '1.0')                   # Service
                                                                                
            

        self.register_as_type(CommandableHttpControllerDescriptor, FriendCommandableHttpController)    # Controller 
        self.register_as_type(ServiceDescriptor, HelloFriendService)                       # Service


# Container
class HelloFriendProcess(ProcessContainer):

    def __init__(self):
        super(HelloFriendProcess, self).__init__('hello-friend', 'HelloFriend microservice')
        self._config_path = './config2DComm.yaml'
        self._factories.add(HelloFriendControllerFactory())
        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"
   
# Service
- descriptor: "hello-friend:service: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 controller
- descriptor: "hello-friend:controller:commandable-http:default:1.0"
  swagger:
    enable: true
    auto: true
    route: swagger
    name: Friends service
    description: Commandable REST API
  
# Heartbeat controller
- descriptor: "pip-services:heartbeat-controller:http:default:1.0"
   
# Status controller
- descriptor: "pip-services:status-controller:http:default:1.0"

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


Client

import { ConfigParams, Context } from "pip-services4-components-node";
import { CommandableHttpClient } from "pip-services4-http-node";

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(ctx); // Returns 'Hello, Peter !'
    console.log(data);
}

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

    public async greeting(ctx: Context): Promise<string> {
        return await this.callCommand<string>("greeting", ctx, { name: "Peter" });
    }
}
Not available
import (
	"context"
	"fmt"

	cdata "github.com/pip-services4/pip-services4-go/pip-services4-commons-go/data"
	cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
	clnt "github.com/pip-services4/pip-services4-go/pip-services4-http-go/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", cdata.NewAnyValueMapFromValue(params.Value()))
	if calErr != nil {
		return "", calErr
	}

	return clnt.HandleHttpResponse[string](res, correlationId)
}
Not available
from pip_services4_http.clients import CommandableHttpClient
from pip_services4_components.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 CommandableHttpController, and how to consume it via a CommandableHttpClient and from any app via code.