Fractal | Fractal ADL | Cecilia
 

Overview

This tutorial is an introduction to the Fractal component model, using the Cecilia framework as implementation. It explains informally how to design, implement and deploy component-based applications with Fractal (and with some associated tools), in C, by using a concrete example, namely an extremely minimal web server.

This document is intended for those that do not know Fractal, and want to get an overview of this component model, of its motivations and benefits. If you are in this case, you should read this document first, before reading the Fractal component model specification.

This document is organized as follows:

  • an introduction to the Fractal component model;
  • notes on the design of a component-based web server;
  • description of the implementation of the component-based web server;
  • notes on the way to describe the architecture of the web server in the Cecilia ADL;
  • Appendices containing the source code (C, IDL, ADL).

Download

To get sources of the Comanche example based on Makefile, download one of the following packages:

To get sources of the Comanche example based on Maven, download one of the following packages:

Instructions for compiling and running the example, see the README.txt file.

Introduction

What is Fractal?

Fractal is a modular and extensible component model that can be used with various programming languages to design, implement, deploy and reconfigure various systems and applications, from operating systems to middleware platforms and to graphical user interfaces. Fractal is also a project with several sub projects, dealing with the definition of the model, its implementations, and the implementation of reusable components and tools on top of it.

The Fractal component model heavily uses the separation of concerns design principle. The idea of this principle is to separate into distinct pieces of code or runtime entities the various concerns or aspects of an application: implementing the service provided by the application, but also making the application configurable, secure, available, ...
In particular, the Fractal component model uses three specific cases of the separation of concerns principle: namely separation of interface and implementation, component oriented programming, and inversion of control.
The first pattern, also called the bridge pattern, corresponds to the separation of the design and implementation concerns.
The second pattern corresponds to the separation of the implementation concern into several composable, smaller concerns, implemented in well separated entities called components.
The last pattern corresponds to the separation of the functional and configuration concerns: instead of finding and configuring themselves the components and resources they need, Fractal components are configured and deployed by an external, separated entity.

The separation of concerns principle is also applied to the structure of the Fractal components. A Fractal component is indeed composed of two parts: a content that manages the functional concerns, and a controller that manages zero or more non functional concerns (introspection, configuration, security, transactions, ....).
The content is made of other Fractal components, i.e. Fractal components can be nested at arbitrary levels (Fractal components can also be shared, i.e. be nested in several components at the same time).
The introspection and configuration interfaces that can be provided by the controllers allow components to be deployed and reconfigured dynamically.
These control interfaces can be used either programmatically, or through tools based on them, such as deployment or supervision tools.

More information about Fractal, including the complete specification of the component model, and several tutorials, can be found at http://fractal.objectweb.org.

Summary

The main characteristics of the Fractal model are recursion, reflexion and sharing. The Fractal project is made of four sub projects: model, implementations, components and tools.

Design

Before programming a component based software system with Fractal, one must first design it with components and, in particular, identify the components to be implemented.
Note that this component oriented design task is quite independent from the subsequent component oriented programming task: at programming time, it is possible to merge several or even all the design time components into a single, monolithic piece of code, if desired (but then, of course, one loses the advantages of component oriented programming: modularity, adaptability, ...).

This section explains how to design component based applications, by using a concrete example. The next sections reuse this example to illustrate how to implement and deploy Fractal component based applications. This example application is Comanche, an extremely minimal HTTP server. A classical, non component oriented, threaded implementation of this application, in pseudo-code, is shown below:

Server {
  Socket server_socket;
  Socket worker;

  while true do
    server_socket = create server socket
    worker_socket = accept(server_socket, port)
    // accept is a blocking call, it returns only when it has accepted a connection
    create new worker thread, passing it worker_socket
  end while
}

WorkerThread {
  read request on socket

  print first line of request
  // analyse request
  if (request starts with "GET /") then
    get actual filename
    // up to the second " " of the first line of a well-formed request

    if ((file exists) and (file is not special, e.g. a directory)) then
      write "HTTP/1.0 200 OK\n\n" to socket
      write contents of file to socket
    else
      write "HTTP/1.0 404 Not Found\n\n" to socket
      write e.g. "<html> Document not found.</html>" to socket
    end if
  end if
  close socket
}

As can be seen from the source code, this server accepts connections on a server socket and, for each connection, starts a new thread to handle it. Each connection is handled in two steps: the request is analyzed and logged to the standard output, and then the requested file is sent back to the client (or an error is returned if the file is not found).

Finding the components

In a component based application, some components are dynamic, i.e they can be created and destroyed dynamically, possibly quite frequently, while other components are static, i.e. their life time is equal to the life time of the application itself. The dynamic components generally correspond to data, while the static ones generally correspond to services.

In order to identify components in an application, it is easier to begin by identifying its static components. In other words, one should begin by identifying the services that are used in the application.
In the case of Comanche, we can immediately identify two main services, namely a request receiver service and a request processor service. But it is also possible to identify other, lower level services.
For example, we can see that the request receiver service uses a thread factory service, to create a new thread for each request. This thread factory service can be generalized into a scheduler service that can be implemented in several ways: sequential, multi thread, multi thread with a thread pool, and so on.
Likewise, we can see that the request processor uses a request analyzer service, and a logger service, before effectively responding to a request. This response is itself constructed by using a file server service, or an error manager service. This can be generalized into a request dispatcher service that dispatches requests to several request handlers sequentially, until one handler can handle the request (we can then imagine file handlers, servlet handlers, and so on).

The caller graph & call graph extraction functionality of the powerful Doxygen tool (free software) can help determining services.

After the services have been specified, one can look for the main data structures, in order to identify the dynamic components. But the identification of the dynamic components is not mandatory, and is generally not done, because dynamic components themselves are rarely used (this means that, at programming time, data structures are generally not implemented as components, but as ordinary structs).
Indeed components do not have many benefits in the case of highly dynamic, short lived structures (introspection and dynamic reconfiguration, for instance, are not very useful in this case). In the case of Comanche we can consider sockets, HTTP requests, files, streams, and even threads as such data structures. But we will not map them to dynamic components.

After the services have been specified, one must assign them to components. Each component can provide one or more services but, unless two services are very strongly coupled, it is better to use one component per service.
In the case of Comanche, we will use one component per service. We therefore have the seven following components: request receiver, request analyzer, request dispatcher, file request handler, error request handler, scheduler and logger.

Defining the component architecture

After the components have been identified, it is easy to find the dependencies between them, and to organize them into composite components. Indeed the service dependencies are generally identified at the same time as the services themselves. If it is not the case, dependencies can also be found by looking at some use cases or scenarios.
Likewise, services are generally identified from high level to lower level services (or vice versa). It is then easy to find the dependencies and the abstraction level of each component, since the components correspond to the previous services.

This is particularly clear in the case of Comanche: indeed, by re-reading the previous section, one can see that the service dependencies have already been identified. For example:

  • the request receiver service uses the scheduler service and the request analyzer (or the request receiver service uses the scheduler and the scheduler uses the request analyzer);
  • the request analyzer uses the request dispatcher;
  • the request dispatcher itself uses the file and error request handlers.

One can also see that the abstraction levels have already been found:

  • the request receiver is ``made of'' the request receiver itself, plus the scheduler;
  • the request processor is made of the request analyzer, the logger, and the request handler;
  • the request handler is itself made of the request dispatcher, and of the file and error request handlers.

All this can be summarized in the following component architecture:

Architecture of the Cecilia Comanche example.

Defining the component contracts

After the services have been found and organized into components, and after the component hierarchy and the component dependencies have been found, only one thing remains to be done to finish the design phase, namely to define precisely the contracts between the components, at the syntactic and semantic level (if possible with a formal language - such as pre and post conditions, temporal logic formulas, and so on). Classical object oriented design tools, such as scenarios and use cases, can be used here.

The component contracts must be designed with care, so as to be the most stable as possible (changing a contract requires to change several components, and is therefore more difficult than changing a component). In particular, these contracts must deal only with the services provided by the components: nothing related to the implementation or configuration of the components themselves should appear in these contracts.
For example, a setLogger operation has nothing to do in a component contract definition: this operation is only needed to set a reference between a component and a logger component. In other words, contracts must be defined with separation of concerns in mind (see section Introduction): contracts must deal only with functional concerns; configuration concerns will be dealt with separately, as well as other concerns such as security, life cycle, transactions...

In the case of Comanche, we will use minimalistic contracts, defined in Cecilia IDL:

  • The logger service will provide a single log method, with a single parameter representing a string;
  • The scheduler component will provide a single schedule method, with a single parameter representing a pointer to a struct containing a way to run a function with its parameters (function pointer + pointer to arguments), and also possibly other implementation-defined data.
    The role of this schedule method is to execute the given entity at some time after the method has been called, possibly in a different thread than the caller thread;
  • The request analyzer, the request dispatcher and the file and error request handlers will all provide a single handleRequest method, with a single parameter representing a socket file descriptor or a char *. This single handleRequest method will be implemented in several ways, in the different components:
    • the request analyzer will read the request to get the requested URL;
    • the request dispatcher will forward each request to its associated request handlers, sequentially, until one request handler successfully handles the request;
    • the file request handler will try to find and to send back to the client the file whose name corresponds to the requested URL, if it exists;
    • the error request handler will send back to the client an error message, and will always succeed.

Summary

The first step to design a component based application is to define its components. This is done by finding the services used in this application.
The second step is to find the dependencies and hierarchical levels of these components.
The last step is to define precisely the contracts between the components.

Implementation

This section explains how to program Fractal component based applications, by using the Comanche example. It also introduces and motivates the concepts and APIs of Fractal that are used.

Choosing the components' granularity

As explained in section Design, component oriented design is quite independent from component oriented implementation. In particular, at programming time, it is possible to merge several or even all the design time components into a single, monolithic piece of code, if desired.
For example, in the case of Comanche, one may choose to implement all the design time components into a single class, as shown in section Design.
One may also choose to implement each component in its own class, or to implement some components in their own class, and to merge some other components (such as the request dispatcher and its associated request handlers) into a single class.

Using one runtime component per design time component gives maximum flexibility and extensibility, but can be less efficient that merging several design time components into a single runtime component. When in doubt, the first solution should be preferred: optimizations can be done later on, if needed.
In the case of Comanche, we will therefore use one runtime component per design time component.

Implementing the component interfaces

Before implementing the component themselves, the first step is to implement their interfaces. Indeed the Fractal component model requires a strict separation between interfaces and implementation for all components (see section Introduction).
This design pattern is indeed useful to easily replace one component implementation with another. It also offers the possibility to add interposition objects between a client and a component implementation, in order to transparently manage some non functional concerns of the component (as in the Enterprise Java Beans model).
The only drawback of this design pattern is that it is a little less efficient than a solution without interfaces. This is why it may sometimes be needed to merge several design time components into a single Fractal component.

The component interfaces can be implemented easily, since most, if not all, of the work has been done during the definition of the component contracts. In the case of Comanche, three interfaces must be implemented. They are given below (see Appendix Comanche source code for the most importants bits of the source code of Comanche):

interface RequestHandler { int handleRequest (any r); }

interface Scheduler { void schedule(any task); }

interface Logger { void log(string msg); }

Here, anonymous type any was used, but non-component types (such as structs) can be used in interface method parameters. This is not recommended. It is indeed better to use interfaces, even for data structures that are not represented as components: these data structures can then be implemented in various ways (including as components), without needing to change the interfaces that refer to them.
Note also that it would have been better to introduce a request factory component (this was not done for simplification purposes; see the Cecilia Cloneable example for more information on how to do that).

Implementing the components

Now that the component interfaces have been implemented, we can implement the components themselves. The components that do not have any dependencies to other components can be programmed like ordinary C ``modules''. For example, the logger component can be implemented as follows:

#include <stdio.h>

DECLARE_DATA { } ;
#include <cecilia.h>

void METHOD(l, log) (void *_this, char * msg) {
    fprintf(stdout,"Basic logger: log \"%s\"\n",msg);
}

In component oriented programming, and in Fractal in particular, the components that have dependencies to other components must be programmed in a specific way. In Cecilia, this is done using the thinkMC ``language'' (C macros such as METHOD, REQUIRED and CALL).
The Cecilia toolchain generates code to glue the components together. This code glue is compiled and linked with your application, and it has support for one of the goals of Fractal, dynamic reconfigurations.

In fact, in Fractal, a component with dependencies (or bindings) to other components must implement the BindingController interface, defined in the Fractal specification. This interface defines four generic methods listFc, lookupFc, bindFc and unbindFc to manage component bindings.

The listFc method returns the names of the dependencies of the component, and the lookupFc, bindFc and unbindFc methods are used to read, set and unset the corresponding bindings (the s and rh strings do not have to be equal to the names of the corresponding fields).
This enables good distinction between the controller and content part of Fractal components (see section Introduction): the controller part corresponds to the BindingController interface, and the content part to the boot.api.Main interface (in the logger and scheduler components, the controller part was empty).

The listFc, lookupFc, bindFc and unbindFc methods are already implicitly implemented when a Cecilia application is compiled, if the cardinality of the bindings is singleton (a single server interface is bound to a client interface). If the cardinality is collection, you currently have to implement the BindingController methods in your source code. The RequestDispatcher component does it.

In order to do that, you have to know that each binding must have a name of the form prefixpostfix where prefix is common to all the bindings of the collection, and where postfix is arbitrary, but distinct for each binding.
Have a look at the implementation of the RequestDispatcher component, which contains a partial implementation of the collection binding. Once again, this partial implementation was done for simplification purposes, since Maps (Java) / hashes (Perl and most scripting languages) are not part of the standard C language.

Another simplification and "beautification" of the C code was using the request analyzer from the scheduler, instead of using it from the request receiver. Older versions of this tutorial did the latter, so as to be closer to the way the Java (Julia) version of this tutorial is implemented.

ADL based configuration

Configuration and deployment could be done using a program that instantiates components, but this method has several drawbacks:

  • it's not that trivial to program;
  • it's error prone: it's easy to forget a binding or to create a wrong binding.
  • the component architecture is not directly visible (the component's hierarchy description, in particular, is completely lost);
  • most importantly, this method mixes two separate concerns, namely architecture description and deployment. It is impossible to deploy a given component architecture in several ways, without rewriting the configuration/deployment program).

In order to solve these problems, a solution is to use an Architecture Description Language (ADL). As its name implies, an ADL definition describes a component architecture, and only that, i.e. its does not describe the instantiation method. This solves the most important drawback of the previous configuration method.
An ADL is also generally strongly typed, which allows the ADL parser to perform verifications about the declared component architecture. Using an ADL is therefore less error prone and more flexible than using the programmatic approach.

Cecilia ADL is a possible, XML based ADL that can be used to describe Fractal component configurations. Other ADLs can be created if needed (indeed these ADLs are not part of the Fractal component model itself: they are just tools based on this model).
Cecilia ADL is strongly typed. The first step to define a component architecture is therefore to define the types of the components. Each component type must specify what components of this type provide to, and require from other components. For example, the type of the file and error handler components (but also of the request handler and backend components), in Comanche, can be defined as follows (these components provide a RequestHandler interface, and do not have dependencies):

<definition name="comanche.HandlerType">
  <interface name="rh" signature="comanche.RequestHandler" role="server"/>
</definition>

Components with dependencies are declared in a similar way. For example, the type of the request dispatcher component, in Comanche, can be defined as follows:

<definition name="comanche.DispatcherType" extends="comanche.HandlerType">
  <interface name="h" signature="comanche.RequestHandler" cardinality="collection" role="client"/>
  <!-- Need the binding controller because of the "collection" cardinality of interface "h" -->
  <interface name="binding-controller" role="server" signature="fractal.api.BindingController" />
</definition>

Note that this type is declared to extend the previous handler type: this means that the provided and required interface types declared in the handler type are inherited by the dispatcher type. Note also the optional cardinality attribute in the interface type definition: it means that components of this type can have a variable number of bindings (as mentioned above).

After the component types have been defined, the components themselves can be defined. Here Cecilia ADL distinguishes between components that do not expose their content, called primitive components, and components that do expose it, called composite components.

A primitive component is defined by specifying its component type and the C ``module'' that implements it. For example, the file handler component can be defined as follows:

<definition name="comanche.FileHandler" extends="comanche.HandlerType">
  <content class="comanche.FileRequestHandler"/>
</definition>

A composite component is defined by specifying its sub-components, and the bindings between these sub-components. For example, the Comanche composite component, which represents the whole application, and which contains the Frontend and Backend components, can be defined as follows:

<definition name="comanche.Comanche" extends="comanche.ComancheType">
  <component name="fe" definition="comanche.Frontend"/>
  <component name="be" definition="comanche.Backend"/>
  <!-- This component contains plain C source files -->
  <component name="lib" definition="comanche.Library"/>
  <binding client="fe.rh" server="be.rh"/>
  <binding client="this.main" server="fe.main"/>
</definition>

This definition says that the Comanche component provides a Runnable interface (to start the application), that it contains two sub components named fe and be, that the Runnable interface provided by Comanche (this.r) is provided by the Runnable interface of its frontend sub-component (fe.r), and that the request handler required by the frontend sub-component (fe.rh) is provided by the backend component (be.rh).

Once the application's architecture has been defined, it can be compiled, which gives a C ``module'', in source code. The Cecilia ADL parser performs preliminary verifications to check the architecture and, in particular, to check that there is no missing or invalid binding.

If one wants to change the implementation of one or more of the components used in the program (e.g. use a multi-threaded scheduler instead of a sequential, synchronous one), modifying a few characters in an ADL file is all that's necessary.

Summary

Components must be implemented with an appropriate granularity, resulting from a compromise between adaptability and performance.
Their interfaces must be separated from their implementation (this rule must also be applied for data structures).
The implementation must not contain explicit dependencies to other components to allow both static and dynamic (run-time) reconfigurations.
Components can be configured and deployed in two different ways. The programmatic approach mixes different concerns, and is not that trivial to implement in C. The ADL-based approach correctly separates these concerns.

Conclusion

The Fractal component model uses well known design patterns, and organizes them into a uniform, language independent model, that can be applied to operating systems, middleware platforms or graphical user interfaces.

The Fractal component model brings several benefits:

  • it enforces the definition of a good, modular design;
  • it enforces the separation of interface and implementation, which ensures a minimum level of flexibility;
  • it enforces the separation between the functional, configuration and deployment concerns, which allows the application's architecture to be described separately from the code, for example by using an Architecture Description Language;
  • it allows applications to be instantiated in various ways (from fully optimized but unreconfigurable configurations to less efficient but fully dynamically reconfigurable configurations).

All these features should reduce the development time, and should also increase the reusability of components and component architectures, i.e. they should increase productivity.

The Fractal component model and its associated tools have already been used successfully in a number of applications such as:

  • THINK, a library of Fractal components to build operating system kernels;
  • Speedo, an implementation of the Java Data Object (JDO) specification;
  • Proactive, a middleware for parallel, distributed and multi-threaded computing;
  • Petals, a large middleware for Enterprise Application Integration.

More information about Fractal, including the complete specification of the component model, and several tutorials, can be found at http://fractal.objectweb.org.

Appendix: Comanche source code

Below are the most important bits of the source code of Comanche, without some implementation-specific files (semaphores, wrappers for several POSIX/*nix functions).

Notice the METHOD, CALL, REQUIRED and DECLARE_DATA syntactic sugar.

Component interfaces

File comanche/RequestHandler.idl:

package comanche;

/**
 * The RequestHandler interface provides the handleRequest method,
 * implemented by request handlers.
 */
interface RequestHandler {
    int handleRequest (any r);
}

File comanche/Scheduler.idl:

package comanche;

/**
 * The Scheduler interface provides the schedule method,
 * implemented by schedulers.
 */
interface Scheduler {
    void schedule(any task);
}

File comanche/Logger.idl:

package comanche;

/**
 * The Logger interface provides the log method,
 * implemented by loggers.
 */
interface Logger {
    void log(string msg);
}

Component implementations

File comanche/BasicLogger.c:

#include <stdio.h>

/** Declare component internal data, empty here. */
DECLARE_DATA {
} ;

/** Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>

// -----------------------------------------------------------------------------
// Implementation of the Logger interface.
// -----------------------------------------------------------------------------
/**
 * This method is called by the request analyzer.
 * Cecilia IDL declaration: void log(string msg);
 * @arg msg The message to be logged.
 */
void METHOD(l, log) (void *_this, char * msg) {  
    (void)_this;
    fprintf(stdout,"Basic logger: log \"%s\"\n",msg);
}

File comanche/SequentialScheduler.c:

#include <stdio.h>

/** Declare component internal data, empty here */
DECLARE_DATA {
} ;

/** Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>

#include "FunctionArgTypes.h"


// -----------------------------------------------------------------------------
// Implementation of the Scheduler interface.
// -----------------------------------------------------------------------------
/**
 * This method is called by the request receiver.
 * Cecilia IDL declaration: void schedule(any task);
 */
void METHOD(s, schedule) (void *_this, void * task) {
#ifdef DEBUG
    fprintf(stdout,"SequentialScheduler: schedule\n");
#endif

    // Call function directly (synchronous).
    CALL(REQUIRED.rh, handleRequest, (void *)task);
}

File comanche/MultiThreadScheduler.c:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

/** Declare component internal data, empty here */
DECLARE_DATA {
} ;

/** Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>

#include "FunctionArgTypes.h"




/**
 * Wrapper for worker function, because a pthread_exit() is needed to exit a pthread.
 * This call cannot be put in the worker function, because the worker function has
 * to be independent from the scheduler implementation.
 * Calling pthread_exit() when there's only one thread (sequential scheduler) terminates
 * the program.
 */
void * workerFunctionWrapper(void * args) {
    void * _this = ((WorkerArgs *)args)->_this;
    // Call function directly (synchronous).
    CALL(REQUIRED.rh, handleRequest, ((WorkerArgs *)args)->fd);

    // It's our duty to free our args, because the caller (schedule) has probably already returned.
    free(args);
    pthread_exit(NULL);
}

// -----------------------------------------------------------------------------
// Implementation of the Scheduler interface.
// -----------------------------------------------------------------------------
/**
 * This method is called by the request receiver.
 * Cecilia IDL declaration: void schedule(any task);
 */
void METHOD(s, schedule) (void *_this, void * task) {
    pthread_t thread;
    pthread_attr_t attrs;
    WorkerArgs * wArgs;
#ifdef DEBUG
    fprintf(stdout,"MultiThreadScheduler: schedule\n");
#endif

    wArgs = malloc(sizeof(*wArgs));
    if (wArgs != NULL) {

        // Retrieve arguments from the opaque type passed to this function.
        wArgs->_this = _this;
        wArgs->fd = task;

        // Create worker thread.
        if (// Set "detached" thread attribute. We don't want to join in this function
            // the thread we're creating, otherwise we're a sequential scheduler...
               (pthread_attr_init(&attrs) != 0)
            || (pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED) != 0)
            // Create thread.
            || (pthread_create(&thread, &attrs, workerFunctionWrapper, (void*)wArgs) != 0)
        ) {
            // Error...
            fprintf(stderr,"MultiThreadScheduler: cannot create thread !\n");
            // Free wArgs on this error path.
            free(wArgs);
        }
        else {
#ifdef DEBUG
            fprintf(stdout,"MultiThreadScheduler: thread created.\n");
#endif
        }
        // DON'T free wArgs here, this area might still be used by the worker wrapper/function thread.
        // The worker wrapper frees its argument.
    }
    else {
        fprintf(stderr,"MultiThreadScheduler: cannot allocate memory for worker thread args !\n");
    }
}

File comanche/FileRequestHandler.c:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

/** Declare component internal data, empty here. */
DECLARE_DATA {
} ;

/** Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>

#include "ErrorCodes.h"
#include "FunctionArgTypes.h"

/**
 * The file is read by chunks of this many bytes.
 */
#define BUFFER_SIZE (4096)
/**
 * Beginning of reply when file was found.
 */
#define OKSTRING "HTTP/1.0 200 OK\r\n"


// -----------------------------------------------------------------------------
// Implementation of the RequestHandler interface.
// -----------------------------------------------------------------------------
/**
 * This method is called by the request dispatcher.
 * Cecilia IDL declaration: int handleRequest(any r);
 * @arg r The name of the requested file.
 */
int METHOD(rh, handleRequest) (void *_this, void * r) {  
    (void)_this;
    FILE *f;
    char buffer[BUFFER_SIZE];
    int fd;
    char * request;
    const char * pInBuffer;
    static const char * const OK_STRING = OKSTRING;
    size_t nleft;
    ssize_t nwritten;
    struct stat fileinfo;
    off_t contentlength;

    request = ((RequestHandlerArgs *)r)->request;
    fd = ((RequestHandlerArgs *)r)->fd;

#ifdef DEBUG
    fprintf(stdout,"FileRequestHandler: handleRequest \"%s\"\n",(char *)request);
#endif

    f = fopen(request,"r");

    if (f != NULL) {

        // Get file info, and make sure it's a regular file.
        if (stat(request,&fileinfo) < 0) {
            fclose(f);
            fprintf(stderr,"FileRequestHandler: couldn't stat file.\n");
            return Comanche_Error_reading_file; // THIS RETURNS !
        }
        else {
            if (!(S_ISREG(fileinfo.st_mode))) {
                fclose(f);
                fprintf(stderr,"FileRequestHandler: not a regular file.\n");
                return Comanche_Nonexistent_file; // THIS RETURNS !
            }
            contentlength = fileinfo.st_size;
        }

        // Write beginning of reply.
        nleft = sizeof(OKSTRING) - 1;
        pInBuffer = OK_STRING;
#ifdef DEBUG
        fprintf(stdout,"FileRequestHandler: going to write OKSTRING.\n");
#endif
        while (nleft > 0) {
            if ((nwritten = write(fd, pInBuffer, nleft)) <= 0) {
                if (errno == EINTR) {
                    // Interrupted by sig handler return, retry write.
                    nwritten = 0;
                }
                else {
                    // Handle gracefully other errors, for example EPIPE (broken pipe).
                    fclose(f);
                    fprintf(stderr,"FileRequestHandler: couldn't write OKSTRING.\n");
                    return Comanche_Error_writing_socket; // THIS RETURNS !
                }
            }
            nleft -= nwritten;
            pInBuffer += nwritten;
        }
#ifdef DEBUG
        fprintf(stdout,"FileRequestHandler: wrote OKSTRING.\n");
#endif


        // Write Content-Length header, and extra CRLF.
        sprintf(buffer, "Content-Length: %ld\r\n\r\n", contentlength);
        nleft = strlen(buffer);
        pInBuffer = buffer;
#ifdef DEBUG
        fprintf(stdout,"FileRequestHandler: going to write Content-Length.\n");
#endif
        while (nleft > 0) {
            if ((nwritten = write(fd, pInBuffer, nleft)) <= 0) {
                if (errno == EINTR) {
                    // Interrupted by sig handler return, retry write.
                    nwritten = 0;
                }
                else {
                    // Handle gracefully other errors, for example EPIPE (broken pipe).
                    fclose(f);
                    fprintf(stderr,"FileRequestHandler: couldn't write Content-Length.\n");
                    return Comanche_Error_writing_socket; // THIS RETURNS !
                }
            }
            nleft -= nwritten;
            pInBuffer += nwritten;
        }
#ifdef DEBUG
        fprintf(stdout,"FileRequestHandler: wrote Content-Length.\n");
#endif


        // Write contents of file.
        while (1) {
            if (feof(f)) {
                // Success: message and file sent correctly.
                fclose(f);
#ifdef DEBUG
                fprintf(stdout,"FileRequestHandler: feof was true.\n");
#endif
                return Comanche_ErrorCode_OK; // THIS RETURNS !
            }
            else if (ferror(f)) {
                // An error occurred.
                fclose(f);
                fprintf(stderr,"FileRequestHandler: error reading file.\n");
                return Comanche_Error_reading_file; // THIS RETURNS !
            }
#ifdef DEBUG
            fprintf(stdout,"FileRequestHandler: going to read a chunk of file.\n");
#endif
            nleft = fread((void *)buffer, 1, BUFFER_SIZE, f);
#ifdef DEBUG
            fprintf(stdout,"FileRequestHandler: read %d bytes\n",nleft);
#endif
            pInBuffer = buffer;
            while (nleft > 0) {
#ifdef DEBUG
                fprintf(stdout,"FileRequestHandler: going to write file to socket.\n");
#endif
                if ((nwritten = write(fd, pInBuffer, nleft)) <= 0) {
                    if (errno == EINTR) {
                        // Interrupted by sig handler return, retry write.
                        nwritten = 0;
                    }
                    else {
                        // Handle gracefully other errors, for example EPIPE (broken pipe).
                        fclose(f);
                        fprintf(stderr,"FileRequestHandler: couldn't write file to socket.\n");
                        return Comanche_Error_writing_socket; // THIS RETURNS !
                    }
                }
                nleft -= nwritten;
                pInBuffer += nwritten;
            }
#ifdef DEBUG
            fprintf(stdout,"FileRequestHandler: written a chunk of file to socket.\n");
#endif
        }
    }
#ifdef DEBUG
    fprintf(stdout,"FileRequestHandler: file does not exist.\n");
#endif
    return Comanche_Nonexistent_file;
}

#undef OKSTRING
#undef BUFFER_SIZE

File comanche/ErrorRequestHandler.c:

#include <stdio.h>
#include <errno.h>
#include <unistd.h>

/** Declare component internal data, empty here. */
DECLARE_DATA {
} ;

/** Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>

#include "ErrorCodes.h"
#include "FunctionArgTypes.h"

/**
 * Reply when no file was found.
 */
#define ERRORSTRING "HTTP/1.0 404 Not found\n\n<html>Document not found.</html>\n"
// -----------------------------------------------------------------------------
// Implementation of the RequestHandler interface.
// -----------------------------------------------------------------------------
/**
 * This method is called by the request dispatcher.
 * Cecilia IDL declaration: int handleRequest(any r);
 * @arg r The name of the requested file.
 */
int METHOD(rh, handleRequest) (void *_this, void * r) {
    (void)_this;
    int fd;
    char * request;
    const char * pInBuffer;
    static const char * const ERROR_STRING = ERRORSTRING;
    size_t nleft;
    ssize_t nwritten;

    request = ((RequestHandlerArgs *)r)->request;
    fd = ((RequestHandlerArgs *)r)->fd;

#ifdef DEBUG
    fprintf(stdout,"ErrorRequestHandler: handleRequest \"%s\"\n",(char *)request);
#endif

    pInBuffer = ERROR_STRING;
    nleft = sizeof(ERRORSTRING) - 1;
    while (nleft > 0) {
        if ((nwritten = write(fd, pInBuffer, nleft)) <= 0) {
            if (errno == EINTR) {
                // Interrupted by sig handler return, retry write.
                nwritten = 0;
            }
            else {
                return Comanche_Error_writing_socket; // THIS RETURNS !
            }
        }
        nleft -= nwritten;
        pInBuffer += nwritten;
    }

    return Comanche_ErrorCode_OK; // Success.
}

#undef ERRORSTRING

File comanche/RequestDispatcher.c:

#include <stdio.h>
#include <string.h>

/** Declare component internal data */
DECLARE_DATA {
    // Quick & dirty implementation for now.
    // To make things more generic, an associative array should be used.
    // Two 3-clause-BSD-licensed (GPL-compatible and therefore LGPL-compatible)
    // implementations can be found through http://en.wikipedia.org/wiki/Associative_array#C
    // * http://www.cl.cam.ac.uk/~cwc22/hashtable/
    // * http://uthash.sourceforge.net/

    // We hold references to components implementing the comanche.RequestHandler interface.
    // The Cecilia IDL to C name mapping convention requires that "comanche.RequestHandler"
    // is turned into "Rcomanche_RequestHandler", which is a valid C identifier, unlike
    // "comanche.RequestHandler".
    /** Reference to a request handler (exact binding defined in a Cecilia ADL file) */
    Rcomanche_RequestHandler * h0;
    /** Reference to a request handler (exact binding defined in a Cecilia ADL file) */
    Rcomanche_RequestHandler * h1;
} ;

/** Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>

#include "ErrorCodes.h"


// -----------------------------------------------------------------------------
// Implementation of the RequestHandler interface.
// -----------------------------------------------------------------------------
/**
 * This method is called by the request analyser.
 * Cecilia IDL declaration: int handleRequest(any r);
 */
int METHOD(rh, handleRequest) (void *_this, void * r) {
    int result;
#ifdef DEBUG
    fprintf(stdout,"RequestDispatcher: handleRequest\n");
#endif

    // Quick & dirty implementation for now.
    if (DATA.h0 == NULL) {
        return Comanche_Tried_calling_NULL; // Error, not supposed to be NULL.
    }
    result = CALL(DATA.h0, handleRequest, r);
    // If the first handler could not handle the first request,
    // check why, and call the second handler if necessary.
    if (result == Comanche_Nonexistent_file) {
        if (DATA.h1 == NULL) {
            return Comanche_Tried_calling_NULL; // Error, not supposed to be NULL.
        }
        result = CALL(DATA.h1, handleRequest, r);
    }
    else {
        // Otherwise, either it succeeded, or an error occurred.
        // In both cases, bail out.
    }

    return result;
}


// -----------------------------------------------------------------------------
// Implementation of the BindingController interface. This interface can be
// reached through predefined name "binding_controller".
//
// The BindingController interface must be implemented by the request dispatcher
// because of the "collection" cardinality of the dependency to handlers.
// -----------------------------------------------------------------------------

/**
 * Implements BindingController listFc method (implementation based on
 * an implementation of listFc found in the library).
 * Cecilia IDL declaration: int listFc(const in string[] clientItfNames);
 */
int METHOD(binding_controller, listFc)(void* _this, const char* clientItfNames[]) {
    (void)_this;
    unsigned int i;
    // Quick & dirty implementation for now.
    static const char * const itfNames[] = {"h0", "h1"};

    if (clientItfNames != NULL) {
        for (i = 0; i < (sizeof(itfNames)/sizeof(itfNames[0])); i++) {
            clientItfNames[i] = (char *)itfNames[i];
        }
    }

    return (sizeof(itfNames)/sizeof(itfNames[0]));
}

/**
 * Implements BindingController lookupFc method.
 * Cecilia IDL declaration: int lookupFc(const in string clientItfName, out any interfaceReference);
 */
int METHOD(binding_controller, lookupFc)(void* _this, const char* clientItfName, void** interfaceReference) {
    // Quick & dirty implementation for now.
    if (!(strcmp(clientItfName,"h0"))) {
        *interfaceReference = (void *)(DATA.h0);
    }
    else if (!(strcmp(clientItfName,"h1"))) {
        *interfaceReference = (void *)(DATA.h1);
    }
    else {
        return fractal_api_ErrorConst_NO_SUCH_INTERFACE;
    }
    return fractal_api_ErrorConst_OK;
}

/**
 * Implements BindingController bindFc method.
 * Cecilia IDL declaration: int bindFc(const in string clientItfName, in any serverItf);
 */
int METHOD(binding_controller, bindFc)(void* _this, const char* clientItfName, void* serverItf) {
    // Quick & dirty implementation for now.
    if (!(strcmp(clientItfName,"h0"))) {
        DATA.h0 = (Rcomanche_RequestHandler *)serverItf;
    }
    else if (!(strcmp(clientItfName,"h1"))) {
        DATA.h1 = (Rcomanche_RequestHandler *)serverItf;
    }
    else {
        return fractal_api_ErrorConst_NO_SUCH_INTERFACE;
    }
    return fractal_api_ErrorConst_OK;
}

/**
 * Implements BindingController unbindFc method.
 * Cecilia IDL declaration: int unbindFc(const in string clientItfName);
 */
int METHOD(binding_controller, unbindFc)(void* _this, const char* clientItfName) {
    if (!(strcmp(clientItfName,"h0"))) {
        DATA.h0 = (Rcomanche_RequestHandler *)NULL;
    }
    else if (!(strcmp(clientItfName,"h1"))) {
        DATA.h1 = (Rcomanche_RequestHandler *)NULL;
    }
    else {
        return fractal_api_ErrorConst_NO_SUCH_INTERFACE;
    }
    return fractal_api_ErrorConst_OK;
}

File comanche/RequestAnalyzer.c:

/** Declare component internal data, empty here. */
DECLARE_DATA {
} ;

/** Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>

#include "ErrorCodes.h"
#include "FunctionArgTypes.h"
#include "csapp.h"

/**
 * Maximum length of a request (the request is truncated if it is longer than
 * REQUEST_LEN bytes).
 */
#define REQUEST_LEN (8191)

/**
 * SIGPIPE handler: handle SIGPIPE gracefully (the default handler kills the process).
 */
static void SIGPIPE_handler(int val) {
    fprintf(stderr,"Pipe incorrectly closed (errno = %d, handler argument = %d) !\n",errno,val);
}


// -----------------------------------------------------------------------------
// Implementation of the RequestHandler interface.
// -----------------------------------------------------------------------------
/**
 * This method is called by the request receiver.
 * Cecilia IDL declaration: int handleRequest(any r);
 */
int METHOD(a, handleRequest) (void *_this, void * r) {
#ifdef DEBUG
    fprintf(stdout,"RequestAnalyzer: handleRequest\n");
#endif
    int connectfd;
    char request[REQUEST_LEN+1];
    int nread;
    unsigned int i;
    char * ptr;
    RequestHandlerArgs args;
    int retval;
    handler_t * oldSIGPIPE;

    // Redirect SIGPIPE handler for this thread.
    if ((oldSIGPIPE = Signal(SIGPIPE,SIGPIPE_handler)) != (handler_t *)-1) {

        // Retrieve file descriptor, read on it.
        connectfd = (int)(intptr_t)r; // r was converted from an int, and sizeof(int) <= sizeof(void*), so this is valid.
        nread = read(connectfd,(void *)request, REQUEST_LEN);

        if (nread >= 0) {
            // We read something.
            if (nread == REQUEST_LEN) {
                fprintf(stderr,"RequestAnalyzer: truncating long request.\n");
            }
            request[nread] = '\0'; // Null-terminate string.

            // We want to read only the first line: truncate string at first CR or LF.
            ptr = request;
            for (i = 0; i < (unsigned int)nread; i++) {
                char c = *ptr++;
                if ((c == '\n') || (c == '\r')) {
                    *(ptr-1) = '\0';
                    break;
                }
            }

            // Log the request.
            CALL(REQUIRED.l, log, request);

            // Does the request start with "GET /" ?
            if (!strncmp(request,"GET /",5)) {
                // Does the rest of the line contain a space ?
                char * space = strchr((char *)request+5, ' ');
                if (space != NULL) {
                    // Request looks well-formed enough. Null-terminate it.
                    *space = '\0';
                    args.request = request+5;
                    args.fd = connectfd;
                    // Pass request to the request dispatcher.
                    retval = CALL(REQUIRED.rh, handleRequest, (void *)&args);
                }
            }
        }
        else {
            retval = Comanche_Error_reading_socket;
            fprintf(stderr,"RequestAnalyzer: error reading from socket !\n");
        }

        // Don't forget to close the socket.
        close(connectfd);

        // Don't forget to restore the old handler.
        Signal(SIGPIPE,oldSIGPIPE);
        retval = Comanche_ErrorCode_OK;
    }
    else {
        retval = Comanche_Error_reading_socket;
        fprintf(stderr,"RequestAnalyzer, handleRequest: cannot redirect SIGPIPE, aborting.\n");
    }
    return retval;
}

#undef REQUEST_LEN

File comanche/RequestReceiver.c:

#include <pthread.h>

/** Declare component internal data, empty here */
DECLARE_DATA {
} ;


/** Include cecilia.h. Must be included after the DECLARE_DATA */
#include <cecilia.h>

#include "FunctionArgTypes.h"
#include "csapp.h"
#include "Semaphore.h"


// Global variables that cannot be inclued in the internal component data
// because the "DATA." construct uses "_this", and the signal handlers
// cannot be passed the _this variable (they're called by the OS).
// (One could always put a global variable containing the value of _this
// for this component, but this is bad.)
/**
 * Semaphore used to ensure mutual exclusion of calls to the signals (it is
 * initialized with a waiting queue of 1, making it a mutex).
 */
Semaphore semSignals;

/**
 * Boolean indicating that a SIGINT or SIGTERM has already been handled.
 */
int sigHandled;

/**
 * Buffer used by setjmp() and longjmp(), in order to execute cleanup.
 * Using setjmp and longjmp is more portable (and safer ?) than using
 * GCC-specific __attribute__((constructor)) / __attribute__((constructor)).
 */
jmp_buf saveState;


/**
 * SIGPIPE handler: handle SIGPIPE gracefully (the default handler kills the process).
 * Semaphores are used to force mutual exclusion of the signal handler (though it shouldn't
 * matter much for this one).
 */
static void SIGPIPE_handler(int val) {
    int errno_saved; // Save and restore system global variable errno (its value may be changed asynchronously).
    errno_saved = errno;
    semP(&semSignals);
    fprintf(stderr,"Received signal %d (SIGPIPE): pipe incorrectly closed (errno = %d) !\n",val,errno_saved);
    semV(&semSignals);
    errno = errno_saved;
}

/**
 * SIGINT handler: execute cleanup code.
 * Semaphores are used to force mutual exclusion of the signal handler (it is important that
 * assignments to sigHandled are atomic).
 */
static void SIGINT_handler(int val) {
    semP(&semSignals);
    if (sigHandled == 0) {
        sigHandled = 1;
        semV(&semSignals);
        fprintf(stderr,"Received signal %d (SIGINT): CTRL+C, exiting !\n",val);
        longjmp(saveState,1);
    }
    semV(&semSignals);
}

/**
 * SIGTERM handler: execute cleanup code.
 * Semaphores are used to force mutual exclusion of the signal handler (it is important that
 * assignments to sigHandled are atomic).
 */
static void SIGTERM_handler(int val) {
    semP(&semSignals);
    if (sigHandled == 0) {
        sigHandled = 1;
        semV(&semSignals);
        fprintf(stderr,"Received signal %d (SIGTERM): process termination request, exiting !\n",val);
        longjmp(saveState,1);
    }
    semV(&semSignals);
}


// -----------------------------------------------------------------------------
// Implementation of the boot interface.
// -----------------------------------------------------------------------------
/**
 * This method is the user entry point of the program.
 * Cecilia IDL declaration: int main(int argc, string[] argv);
 */
int METHOD(main, main) (void *_this, int argc, char *argv[]) {  
    (void)argc, (void)argv;
    int listenfd = -1;
    int connectfd;
    struct sockaddr_in clientaddr;
    handler_t * oldSIGPIPE;
    handler_t * oldSIGINT;
    handler_t * oldSIGTERM;

    sigHandled = 0;
    semInit(&semSignals, 1); // This semaphore is a mutex.

    // Redirect signals.
    if (   ((oldSIGINT = Signal(SIGINT,SIGINT_handler)) != (handler_t *)-1)
        && ((oldSIGTERM = Signal(SIGTERM,SIGTERM_handler)) != (handler_t *)-1)
        && ((oldSIGPIPE = Signal(SIGPIPE,SIGPIPE_handler)) != (handler_t *)-1)
       ) {

        if (setjmp(saveState) == 0) {
            // This is the first time setjmp returns.
            fprintf(stdout,"Hello, Comanche Web Server starting :-)\n");

            // Create server socket, listening on port 8080.
            listenfd = open_listenfd(8080);
            if (listenfd >= 0) {
                // Socket creation succeeded, we can proceed.
                while (1) {
                    unsigned int clientlen = sizeof(clientaddr);
                    connectfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen);
                    if (connectfd >= 0) {
                        // Pass file descriptor to scheduler.
                        CALL(REQUIRED.s, schedule, (void *)(intptr_t)connectfd); // sizeof(int) <= sizeof(void *), so this conversion is valid.
                    }
                    else {
                        fprintf(stderr,"RequestReceiver: failed accepting a connection !\n");
                    }
                }
            }
            else {
                fprintf(stderr,"RequestReceiver: failed to create server socket !\n");
            }
        }
        else {
            // setjmp has returned non-zero, which means one of the SIGINIT or SIGTERM handlers
            // has been executed: cleanup.
            if (listenfd >= 0) {
                if (close(listenfd) < 0) {
                    fprintf(stderr,"RequestReceiver: failed to close server socket !\n");
                }
            }
        }
        // Restore signals.
        Signal(SIGPIPE,oldSIGPIPE);
        Signal(SIGINT,oldSIGINT);
        Signal(SIGTERM,oldSIGTERM);
    }
    else {
        fprintf(stderr,"RequestReceiver: failed to redirect signals !\n");
    }

    fprintf(stdout,"Comanche Web Server stopping, bye :-)\n");
    fflush(stderr);
    fflush(stdout);

    semDeInit(&semSignals);

    return 0;
}

See the rest of source codes in the Cecilia distribution.

Appendix: Comanche architecture definition

Here are the Cecilia ADL definitions describing the Comanche architecture:

Component types

File comanche/SchedulerType.fractal:

<definition name="comanche.SchedulerType">
  <interface name="s" signature="comanche.Scheduler" role="server"/>
  <interface name="rh" signature="comanche.RequestHandler" role="client"/>
</definition>

File comanche/HandlerType.fractal:

<definition name="comanche.HandlerType">
  <interface name="rh" signature="comanche.RequestHandler" role="server"/>
</definition>

File comanche/ReceiverType.fractal:

<definition name="comanche.ReceiverType">
  <interface name="s" signature="comanche.Scheduler" role="client"/>
  <!-- 'main' interface. The entry point of the application -->
  <interface name="main" role="server" signature="boot.api.Main" />
</definition>

File comanche/AnalyzerType.fractal:

<definition name="comanche.AnalyzerType">
  <interface name="a" signature="comanche.RequestHandler" role="server"/>
  <interface name="l" signature="comanche.Logger" role="client"/>
  <interface name="rh" signature="comanche.RequestHandler" role="client"/>
</definition>

File comanche/LoggerType.fractal:

<definition name="comanche.LoggerType">
  <interface name="l" signature="comanche.Logger" role="server"/>
</definition>

File comanche/DispatcherType.fractal:

<definition name="comanche.DispatcherType" extends="comanche.HandlerType">
  <interface name="h" signature="comanche.RequestHandler" cardinality="collection" role="client"/>
  <!-- Need the binding controller because of the "collection" cardinality of interface "h" -->
  <interface name="binding-controller" role="server" signature="fractal.api.BindingController" />
</definition>

File comanche/FrontendType.fractal:

<definition name="comanche.FrontendType">
  <interface name="rh" role="client" signature="comanche.RequestHandler" />
  <!-- 'main' interface. The entry point of the application -->
  <interface name="main" role="server" signature="boot.api.Main" />

</definition>

File comanche/ComancheType.fractal:

<definition name="comanche.ComancheType">
  <!-- 'main' interface. The entry point of the Comanche application -->
  <interface name="main" role="server" signature="boot.api.Main" />
</definition>

Primitive components

File comanche/SequentialScheduler.fractal:

<definition name="comanche.SequentialScheduler" extends="comanche.SchedulerType">
  <content class="comanche.SequentialScheduler"/>
</definition>

File comanche/MultiThreadScheduler.fractal:

<definition name="comanche.MultiThreadScheduler" extends="comanche.SchedulerType">
  <content class="comanche.MultiThreadScheduler"/>
</definition>

File comanche/Receiver.fractal:

<definition name="comanche.Receiver" extends="comanche.ReceiverType">
  <content class="comanche.RequestReceiver"/>
</definition>

File comanche/Analyzer.fractal:

<definition name="comanche.Analyzer" extends="comanche.AnalyzerType">
  <content class="comanche.RequestAnalyzer"/>
</definition>

File comanche/Logger.fractal:

<definition name="comanche.Logger" extends="comanche.LoggerType">
  <content class="comanche.BasicLogger"/>
</definition>

File comanche/Dispatcher.fractal:

<definition name="comanche.Dispatcher" extends="comanche.DispatcherType">
  <content class="comanche.RequestDispatcher"/>
</definition>

File comanche/FileHandler.fractal:

<definition name="comanche.FileHandler" extends="comanche.HandlerType">
  <content class="comanche.FileRequestHandler"/>
</definition>

File comanche/ErrorHandler.fractal:

<definition name="comanche.ErrorHandler" extends="comanche.HandlerType">
  <content class="comanche.ErrorRequestHandler"/>
</definition>

Composite components

File comanche/Handler.fractal:

<definition name="comanche.Handler" extends="comanche.HandlerType">
  <component name="rd" definition="comanche.Dispatcher"/>
  <component name="frh" definition="comanche.FileHandler"/>
  <component name="erh" definition="comanche.ErrorHandler"/>
  <binding client="this.rh" server="rd.rh"/>
  <binding client="rd.h0" server="frh.rh"/>
  <binding client="rd.h1" server="erh.rh"/>
</definition>

File comanche/Backend.fractal:

<definition name="comanche.Backend" extends="comanche.HandlerType">
  <component name="ra" definition="comanche.Analyzer"/>
  <component name="rh" definition="comanche.Handler"/>
  <component name="l" definition="comanche.Logger"/>
  <binding client="this.rh" server="ra.a"/>
  <binding client="ra.l" server="l.l"/>
  <binding client="ra.rh" server="rh.rh"/>
</definition>

File comanche/Frontend.fractal:

<definition name="comanche.Frontend" extends="comanche.FrontendType">
  <component name="rr" definition="comanche.Receiver"/>
  <component name="s" definition="comanche.MultiThreadScheduler"/>
  <!--<component name="s" definition="comanche.SequentialScheduler"/>-->
  <binding client="s.rh" server="this.rh"/>
  <binding client="rr.s" server="s.s"/>
  <binding client="this.main" server="rr.main"/>
</definition>

File comanche/Comanche.fractal:

<definition name="comanche.Comanche" extends="comanche.ComancheType">
  <component name="fe" definition="comanche.Frontend"/>
  <component name="be" definition="comanche.Backend"/>
  <!-- This component contains plain C source files -->
  <component name="lib" definition="comanche.Library"/>
  <binding client="fe.rh" server="be.rh"/>
  <binding client="this.main" server="fe.main"/>
</definition>
 
2007-2009 © ObjectWeb Consortium  | Last Published: 2009-04-21 14:36  | Version: 2.1.0