Table of Contents

Hop

This module contains utilities for getting, controlling, and using the Hop server. The module defines functions to craft service responses, and a broadcast function that lets a server send events to registered remote clients.

The module also defines an API to invoke third party Web Services.

For ease of use, hop is defined as a global object and can be used directly without require.

General configuration

hop.engine

The engine property is used to distinguish Hop from other JavaScript engines when an application is compatible with different systems. With Hop, its value is always the string hop.

hop.isServer

The isServer property is true for code executing on a server and false for code executing on a client.

hop.isWorker

The isWorker property is true if and only if the expression is evaluated within a worker context.

Server Information

The server properties defined below are read-only.

hop.port

The port number of the running Hop server. To set the port and protocol for the Hop server, see config.

console.log( "port:", hop.port );

hop.ports

Returns all the ports number of the running Hop server.

console.log( "port:", hop.ports );

hop.hostname

The host name of the running Hop server.

console.log( "hostname:", hop.hostname );

hop.version

The Hop version.

console.log( "Hop version:", hop.version );

hop.arch

The Hop architecture.

console.log( "Hop arch:", hop.arch );

hop.loginCookieCryptKey

An unique integer seed to for password encryption. This value is shared amongst all Hop workers.

console.log( "seed:", hop.loginCookieCryptKey );

Server Configuration

hop.httpAuthenticationMethod

The Hop HTTP authentication method. Can either be "basic" or "digest".

console.log( "method:", hop.httpAuthenticationMethod );

hop.useProxy

Proxy to be used to access internet resources.

hop.useProxy = "192.168.3.4";

hop.enableProxying

Enable/disable Hop to act as an HTTP proxy.

hop.enableProxying = false;

Responses

Service result values are transformed into Hop responses before being sent to the clients.

hop.HTTPResponseHop( obj, [option] )

This class is used to respond values to client requests.

service getObj() {
  return hop.HTTPResponseHop( { key: "foo", value: [ 1,2 3 ] } );

Note: In normal situations, it is not necessary to explicitly build the HTTPResponseHop object as the runtime system automatically constructs one when the response of a service is a compound JavaScript object.

The options list is:

hop.HTTPResponseXml( obj, [option] )

This class is used to deliver XML documents to client.

service getXml() {
  return hop.HTTPResponseXml( <div>a div</div> );

The options list is:

Note: In normal situations, it is not necessary to explicitly build the HTTPResponseXml object as the runtime system automatically constructs one when the response of a service is an XML fragment. It might be useful to construct an HTTPResponseXML explicitly when a header is to be associated with the response. Example:

service foo() {
   return hop.HTTPResponseXml(
     <html>
       <button onclick=~{console.log( document.cookie )}>show</button>
     </html>,
     { contentType: "text/html", header: { "set-cookie": "a=b; HttpOnly" } } );
}

hop.HTTPResponseString( string, [option] )

This class is used to deliver plain character strings to client.

service getXml() {
  return hop.HTTPResponseString(
    "This resource does not exist here!",
    { startLine: "HTTP/1.0 404 File not found" } ) 

The options list is:

Example

This example shows how to implement an HTTP redirection using `hop.HTTPResponseString".

redirection/redirection.js

service redirection( { x } ) {
   console.log( `${x} http://${this.host}:${this.port}${target( x || 23 )}` );
   return hop.HTTPResponseString( "",
      {
         startLine: "HTTP/1.0 307 See Other",
         header: { "location": `http://${this.host}:${this.port}${target( x || 23 )}` }
      } );
}

service target( x ) {
   return <html>
     I am the final destination: ${x}
   </html>
}
                                          
console.log( "Go to \"http://%s:%d/hop/redirection?x=23\"", hop.hostname, hop.port );

hop.HTTPResponseJson( object )

This convenience function returns an [application/json] value from a JavaScript object. It is the same as:

hop.HTTPResponseString( JSON.stringify( obj ), { contentType: 'application/json' } )

hop.HTTPResponseFile( path, [option] )

This class is used to respond files to clients. The argument path is the full path of a existing file. The option is an object whose fields can be:

Example

This example shows the most efficient way to deliver content file to client. Using a HTTPResponseFile object deliver much better performance than reading the file content first and then seding a buffer or a string the client.

In this example, the file replied to the client is to be interpreted as a plain text file alhgouth it is a JavaScript program. To tell the Web browser not to interpret the file, the mime type text/plain is specified in the response option.

The charset encoding of the file is also provided using the charset attribute.

file/file.js

service file() {
   var pre = <pre/>;

   return <html>
      ~{
         var entityMap = {
            "&": "&amp;",
            "<": "&lt;",
            ">": "&gt;",
            '"': '&quot;',
            "'": '&#39;',
            "/": '&#x2F;'
         };
         
         function escapeHTML( string ) {
            return String( string ).replace(
                  /[&<>"'\/]/g,
               function ( s ) {
                  return entityMap[s];
               } );
         }
      }
      <button onclick=~{
            var file = ${fileGet.resource( "file.js" )};

            ${fileGet}( file )
            .post( function( txt ) {
               ${pre}.innerHTML = escapeHTML( txt )
            } );
      }>
         click me
      </button>
     ${pre}
   </html>;
}

service fileGet( path ) {
   return hop.HTTPResponseFile( path,
                                { contentType: "text/plain",
                                  charset: hop.locale } );
}
                                          
console.log( "Go to \"http://%s:%d/hop/file\"", hop.hostname, hop.port );

Note: HTTPResponseFile is a much faster way to send a file to a client, althought, the same behaviour can also be implemented combining standard fs operations and HTTPResponseString values.

hop.HTTPResponseAuthentication( msg, [request] )

This class is used to respond HTTP 401 Unauthorized response to Web client.

Note: the class hop.HTTPResponseAuthentication is a convenience class. The same behavior can be implemented using hop.HTTPResponseString and passing a startLine value in the optional argument.

Example

This example shows how to use HTTPResponseAuthentication to request Web browser authentication.

The example counts the number of request (the variable count). Each request decrements the counter but only passes through when it reaches 0.

authentication/authentication.js

var count = 2;

service authenticationAccept() {
   switch( count-- ) {
      case 2:
        return hop.HTTPResponseAuthentication( "I don't know you", this );

      case 1:
        return hop.HTTPResponseAuthentication( "Do you really insist?", this );
      
      case 0:
        count = 2;
        return "Ok for this time";
   }
}

service authentication() {
   var console = <div/>;

   return <html>
      <div>
        Click 3 times the "click me" button.
        Permission granted on the third request.
      </div>
      <button onclick=~{
        ${authenticationAccept}()
          .post( function( v ) { ${console}.innerHTML = v },
                 { fail: function( v ) { ; } } ) }>
        click me
      </button>
      ${console}
   </html>
}

console.log( "Go to \"http://%s:%d/hop/authentication\"", hop.hostname, hop.port );

hop.HTTPResponseError( obj )

Respond an error value to the client, which either invokes the fail callback of the post service call, or raises an exception.

hop.HTTPResponseAsync( sender, req )

Asynchronous responses are used when a service cannot returns instantly a value to a client because it relies on an asynchronous computation. In that situation, the service must produce a hop.HTTPResponseAsync which is interpreted by the builtin server as a delayed reply.

Example

This example illustraed service declaration with fixed number of argument and asynchronous responses.

The service foo must call the service bar on the same host. Calling the serving synchronously would result in a dead lock as only one thread is in charge of handling services. The call to bar must then be asynchronous. This is acheived by applying the post method of the frame computed with bar( x + 1 ).

The service foo relies on a asynchronous computation. It then cannot respond immediately to the client. It will be in position to reply only when the invocation of bar has completed. This is implemented using a hop.HTTPResponseAsync object. The argument sendReponse is a function automatically created by the runtime system. When invoked with sendReponse( e ) the result of the service bar is replied to the cilent which has called foo.

svc3/svc3.js

service svc3() {
   return <html>
      <button onclick=~{
         ${foo}( 1 )
            .post( function( r ) {
               document.body.appendChild( r );
            } )
      }>click</button>
    </html>;
}

service foo( x ) {
   console.log( "in foo x=", x );
   return hop.HTTPResponseAsync(
      function( sendResponse ) {
         bar( x + 1 ).post( function( e ) {
            sendResponse( e );
         } )
      }, this );
}

service bar( x ) {
   console.log( "in bar x=", x );
   return <div>${ x + 1 }</div>;
}

console.log( "Go to \"http://%s:%d/hop/svc3\"", hop.hostname, hop.port );

Note: the class hop.HTTPResponseAsync is the base class for implementing asynchronous reponses. Returning Promise objects as a similar behavior and is encouraged. The service foo defined above can be implemented as:

service foo( x ) {
   console.log( "in foo x=", x );
   return new Promise( function( resolve, reject ) {
      bar( x + 1 ).post( resolve );
   }
}

Invoking the resolve function actually sends the responds to the client. Invoking the reject as the same effect of responding a HHTPResponseError value.

hop.HTTPResponseProxy( obj )

The hop.HTTPResponseProxy objects are to be used when a remote resource can be access othwerwise. For instance, these situations arise because of the security enforcement of the Web browsers. Some resources have to be downloaded from the origin server. Using a hop.HTTPResponseProxy object enables the web page to only use local URLs, that are proxied to the actual remote resources by the server.

Example

This example illustrates standard client image manipulations and use of hop.HTTPResponseProxy objects.

Security enforcement of Web browsers prevent manipulation image pixels whose origin differs from the origin of the main page. To workaround this problem, the manipulated image is proxied by the Hop server. The Web browser only sees relative local URLs.

image/image.js

var img_default = "http://t1.gstatic.com/images?q=tbn:ANd9GcRUADxj_7NEl8RAFNM-s6x3Wgp1QIg81QHRMVeOuMHBklr1JWddmQ";

var colors_default = [
   {red: 252, green: 27, blue: 0},
   {red: 125, green: 167, blue: 129},
   {red: 255, green: 122, blue: 10}
];

service imgProxy( url ) {
   return hop.HTTPResponseProxy( url );
}

service image( o ) {
   var url = o && "url" in o ? o.url : img_default;
   
   return <html>
     ~{
        function drawImage( url, colors ) {
           var img = new Image();
           var src = document.getElementById( "src" );
           var dst = document.getElementById( "dst" );
           var ctxsrc = src.getContext( "2d" );
           var ctxdst = dst.getContext( "2d" );

           img.onload = function( e ) {
              src.width = img.width;
              src.height = img.height;
              dst.width = img.width;
              dst.height = img.height;

              ctxsrc.drawImage( img, 0, 0 );
              var f = ctxsrc.getImageData( 0, 0, img.width, img.height );
              ctxdst.putImageData( colorize( f, colors ), 0, 0 );
           }

           img.src = url;
        }

        function colorize( frame, colorset ) {
           var data = frame.data;
           var l = data.length / 4;

           for( var i = 0; i < l; i++ ) {
              var r = data[ i*4 ];
              var g = data[ i*4 + 1 ];
              var b = data[ i*4 + 2 ];
              var range = Math.floor( (0.65*r + 0.22*g + 0.13*b) / 86 );

              var cs = colorset[ range ];
              
              data[ i*4 ] = 0.7*cs.red + 0.3*r;
              data[ i*4 + 1 ] = 0.7*cs.green + 0.3*g;
              data[ i*4 + 2 ] = 0.7*cs.blue + 0.3*b;
           }

           return frame;
        }
        
        window.addEventListener( "load", function( e ) {
           drawImage( ${imgProxy( url )}, ${colors_default} )
        } )
     }
     <img src=${url}/>
     <canvas id="src" style="display: none"/>
     <canvas id="dst"/>
   </html>
}
   
console.log( "Go to \"http://%s:%d/hop/image\"", hop.hostname, hop.port );

Requests

The this value of service invokations is an object that denotes the current request.

hop.isLocalRequest( req )

Returns true if and only if req denotes a locate request, i.e., an http request emitted from the same machine that runs the server. Returns false otherwise.

Request Filtering

Hop services are associated to URL and the server automatically routes requests toward the corresponding service. This mechanism constitutes the basis of Hop web programming and in most situtations, this high-level programming should be sufficient and low-level details such as the configuration of the underlying socket or the details of the http request attributes could be ignored. However, in some other situations a finer control over the connections and requests is needed. If this only consists in obtaining an information about an http connection, the reification of the request the service receives as its this argument should be enough. If this consists in obtaining information independant of services or if this consists in answering a request that is not even associated with a service another programming level is needed. This is acheived by the request filters described in this section.

Note: Request filters can only be defined before the first request is received. Adding filter in the hoprc.js file is then recommended.

hop.addRequestFilter( filter )

hop.addRequestFilterFirst( filter )

hop.addRequestFilterLast( filter )

The function hop.addRequestFilter( filter ) adds a filter, a function of one argument, that will be invoked upon every request received by the server. The function hop.addRequestFilterFirst( filter ) adds a filter that is executed before the existing filter. The function hop.addRequestFilterLast( filter ) adds a filter after the registered filters. All filters are executed in the main worker. Their execution should then be short.

Request filters are chained together and this chaining drives the server behavior. If the value returned from a filter is a response (e.g., HTTPReponseHop, HTTPResponseString, etc.), the server uses this value to respond to the request. If the value is not a response, then the next filter is executed. This process repeats until the a filter returns a response.

Example

This example illustrates standard client image manipulations and use of hop.HTTPResponseProxy objects.

Security enforcement of Web browsers prevent manipulation image pixels whose origin differs from the origin of the main page. To workaround this problem, the manipulated image is proxied by the Hop server. The Web browser only sees relative local URLs.

reqfilter.js

"use strict";

hop.addRequestFilter( req => {
      console.log( "*** req.path=", req.abspath );
      return undefined;
   } );

hop.addRequestFilter( req => {
      if( req.abspath.match( "^/hop" ) ) {
         console.log( "+++ req.header=", req.header );
      }
   } );

service foo() {
   if( !this.socket.ssl ) {
      return "dangerous";
   } else {
      return "secure";
   }
}

Server

Server objects denotes a remote server. They are used to attached listeners to server events (see Broadcast) and to invoke services from server to another server (see service).

new hop.Server( [ hostname [, port [, authorization [, ssl ] ] ] )

Creates a new server object. The arguments are as follows:

Server.addEventListener( eventName, handler [, options] )

Use this method on the client side to register to the eventName server event. The effect of this method is to establish a persistent connection with the Hop server, register the client for the given event type, and trigger the handler whenever the event is received by the client. handler takes one argument, the event. The transmitted value can be retrieved in the value property of the event.

When used within a web browser, connection is established with the Hop server serving the current page, the exact syntax is server.addEventListener( eventName, handler ) where server denotes the current server (the runtime system automatically binds the server variable to the current server).

server.addEventListener( 'refreshScore', function( event ) {
  var score = event.value;
  var scoreElement = this.document.getElementById( 'score' );
  // update GUI element with new score

Two predefined events are automatically sent to clients:

Server.removeEventListener( eventName, handler )

Removes an attached listener.

Broadcast

Broadcast is an abstraction on top of webSockets to let a Hop server send events to connected clients (either web browsers or Hop client processes). Connections originate from the client to the server, so broadcast can be used even in the asymetric web topology where clients most often lie behind a NAT router or firewall and would not accept a connection from a remote server (forbidding the remote server to invoke services running on the client process).

hop.broadcast( eventName, value )

Generates an event of type eventName with payload value. The event is broadcast over the network to all registered clients. eventName is cast into a String, valuecan be any serializable object, including JavaScript objects, Hop.js services, and xml-elements. Clients register to specific broadcast events with the addEventListenermethod.

hop.broadcast( 'refreshScore', 14 );

hop.signal()

This function is similar to broadcast but only one receiver will be notified of the message.

EventMonitor

Event listener monitors are used to react to client connection requests.

hop.eventListenerMonitor( eventName )

Creates a new event monitor on event eventName.

eventListenerMonitor.monitor( eventName )

Add a new event to be monitored by this monitor.

eventListenerMonitor.addEventListener( event, callback )

The argument event can be newListener or removeListener:

Example:

const monitor = new hop.eventListenerMonitor( "foo" );
monitor.monitor( "bar" );

console.log( "monitor=", monitor );

var o = { x: 1, y: 2 };

monitor.addEventListener( "newListener", e => {
   console.log( "newL=", e.data, e.target.header.host );
   setTimeout( _ => hop.broadcast( e.data, { x: 1, y: 2 } ), 2000 );
   if( e.data == "foo" ) {
      setTimeout( _ => {
         console.log( "bcast gee" );
         hop.broadcast( "gee", "dummy" )
      }, 3000 );
   }
} );

monitor.addEventListener( "removeListener", e => console.log( "remL=", e.data, e.target.header.host ) );

Web Service

A WebService reifies an API, i.e., a set of services, that let you invoke third party WebServices the same way you invoke Hop services (see Interoperable WebServices for interoperability notes).

var hop = require( 'hop' );
var mymemory = hop.webService( "http://mymemory.translated.net/api/get" );
mymemory( {q: 'My tailor is rich.', langpair: 'en|fr' } ).post( function( result ) {
   console.log( result.responseData );
   }, { fail: function( error ) {
   console.log( 'failure' );
} });

hop.webService( url )

Use this method to declare a remote WebService,that can later be invoked with named arguments. urlis the url of the WebService. Call the returned function with an object argument containing the named arguments you want to send to the WebService. The returned value is a WebServiceFrame (very similar in use to Service Frames).

WebServiceFrame.post([ success [, fail-or-options]] )

Invokes asynchronously the webService. The optional successargument, when provided, must be a function of one argument, which is set the the value returned by the WebService.

if the optional argument fail-or-options is a procedure, it is invoked if an error occurs during the WebService invocation. If fail-or-options is an object, it contains optional parameters to the WebService invocation.

The list of valid options are:

Example:

var ws = hop.webService( "http://localhost:1337/api/oauth/token" );

ws()
  .postSync(
   { method: "POST",
    header: { "content-type": "application/x-www-form-urlencoded" },
    body: "grant_type=password&client_id=android&client_secret=SomeRandomCharsAndNumbers&username=myapi&password=abc1234" } );

WebServiceFrame.postSync([ success [, fail-or-option]] )

The synchronous version of post. Returns the value returned by the service. Since postSync blocks the execution of the client process until the service returns a value, it is strongly advised to use the asynchronous post when applicable. The options are shared with the post method.

Compiler Driver

The compiler driver provides information about the background compilation status.

hop.compilerDriver

hop.compilerDriver.policy

The compilation policy. The possible values are:

hop.compilerDriver.pending

The number of pending background compilations.

hop.compilerDriver.addEventListener( eventName, handler [, options] )

This method is used to add an listener to the compiler driver. The known events are

hop.compilerDrive.removeEventListener( eventName, handler )

Removes an attached listener.

Miscellaneous

hop.charsetConvert( text, source, target )

Converts the text string from charset source into charset target.

url/url.js

"use hopscript";

var mymemory = hop.webService( "http://mymemory.translated.net/api/get" );

function translateText( text, lang = "en|fr" ) {
   var o = mymemory( { q: text, langpair: lang } ).postSync();
   
   if( o.responseStatus === 200 ) {
      var t = o.responseData.translatedText;
      
      return hop.charsetConvert( unescape( t ), "UTF-8" );
   }
}

service url() {
   var output = <div/>;
   var input = <input value="toto n'est pas content"/>;
   var select = <select>
     <option label="fr->en" value="fr|en">fr-&gt;en</option>
     <option label="en->fr" value="en|fr">en-&gt;fr</option>
   </select>
      
   var translate = service( text, langpair ) {
      return translateText( text, langpair );
   };
      
   return <html>
     <div>
       ${select}
       ${input}
       <button onclick=~{
          ${translate}( ${input}.value, ${select}.value )
             .post( function( v ) { ${output}.innerHTML = v; } )}>
         translate
       </button>
       ${output}
     </div>
   </html>;
}

console.log( "Go to \"http://%s:%d/hop/url\"", hop.hostname, hop.port );

hop.decodeHTML( string )

Decodes an encoded HTML string.

hop.decodeHTML( 'jean &lt;dupont&gt;' );
// "jean <dupont>"

hop.decodeURIComponent( string )

Decodes an encoded URI component.

hop.encodeURIComponent( 'jean dupont' );
// "jean%20dupont"

hop.encodeHTML( string )

Encodes an HTML string into a textual string.

hop.encodeHTML( 'jean <dupont>' );
// "jean &lt;upont&gt;"

hop.encodeURIComponent( string )

Encodes a string into a valid URI component.

hop.encodeURIComponent( 'jean dupont' );
// "jean%20dupont"

hop.Cons()

This function is a constructor to create native (Bigloo) objects.

hop.List()

This function is a constructor to create native (Bigloo) objects.

hop.md5sum( string )

Computes the md5sum of a string.

hop.md5sum( 'jean dupont' );
// "b38bed581de7b86dd6fc8355c73cebf2"

hop.sha1sum( string )

Computes the sha1 sum of a string.

hop.sha1sum( 'jean dupont' );
// "7461340811509ec24dd1c1a32504a01e24423768"

hop.base64encode( string )

Encodes a string into base64.

hop.base64decode( string )

Decodes a base64 string.

hop.compileXML( node [, ofile] [, backend] )

Compile a XML node into HTML. If no output file is specified, the product of the compilation is returned in a buffer. The optional backend argument is a string denoting the HTML version to be used for the compilation.

var node = <html><div onclick=~{alert( "clicked" )}>click me</div></html>
console.log( hop.compileXML( node, false, "html5" ) );

Note: explicit compilation to HTML using hop.compileXML is unncessary for service responses. Services can directly return XML objects in response to HTTP requests.

Sub Modules

The following properties lead to sub modules that can be loaded using the require function.

var hop = require( 'hop' );
var config = require( hop.config );

hop.config

Hop configutation. See config.

hop.csv

Efficient CSV parser. See csv.

hop.feed

RSS manipulation. See feed.

hop.fontifier

hop.hss

See hss.

hop.hopc

API for deadling with the Hop compiler. See hopc.

hop.hopdroid

Hop Android environment. See hopdroid.

hop.markdown

Home brewed markdown parser. See markdown.

hop.notepad

XML widget.

hop.openpgp

Minimalist Openpgp binding. See openpgp.

hop.security

hop.spage

XML widget. See spage.

hop.syslog

Unix syslog. See syslog.

hop.systime

Execution time. See systime.

hop.system

Unix system like command. See system.

hop.tree

hop.texinfo

See texinfo.

See tree.

hop.user

XML widget. See user.

hop.vcf

VCards and other contact formats. See vcf.

hop.wiki

Hop user declaration and manipulation. See tree.

hop.xml

See xml.