Objeto en el navegador que es un proxy para un objeto en un servidor NodeJS

Si tengo una función en mi servidor NodeJS, puedo exponerla para usarla dentro de un navegador web con solo unas pocas líneas de código y luego llamarla desde el navegador con $.getJSON( ) . Voy a dar un ejemplo de esto a continuación. Si quiero estandarizar este tipo de paso de mensajes, hay muchos protocolos a los que podría adherirme, como JSON-RPC 2.0 , y las API que me ayudarían con eso.

Ahora tengo expuestas alrededor de media docena de funciones de este tipo, y se me ocurre que sería bueno agruparlas todas en un solo objeto, pasar eso al servidor para exponerlo todo de una sola vez, luego tener un objeto proxy automáticamente. construido en el lado del cliente haciendo coincidir el prototipo del objeto del servidor y encapsulando las llamadas a $.getJSON(). (Obviamente, esto no estaría exento de limitaciones: una función sincrónica no va a permanecer sincrónica, y cualquier manejo de errores probablemente debería hacerse de una manera estándar para adaptarse a los errores generados por el protocolo de paso de mensajes).

¿Conoce algún software que haga algo como este tipo de representación de objetos de JavaScript para mí? Parece el tipo de cosa que podría existir, aunque mi búsqueda en Google no ha encontrado nada. Podría escribirlo yo mismo, pero me llevaría mucho tiempo hacer un trabajo completo.

Por supuesto, si no hubiera escrito alguna funcionalidad de generación de informes en el lado del cliente y luego hubiera decidido que era realmente tonto y que pertenece al servidor, es posible que nunca hubiera notado la torpe diferencia en la técnica que actualmente necesito para acceder a mi funcionalidad principal desde estos dos lugares ;-)


Y aquí está el ejemplo que prometí de cómo estoy haciendo las cosas en este momento: si tengo una función en mi servidor NodeJS...

function myFunction(arg1, arg2) {
    return arg1 + arg2;
}

...entonces puedo exponerlo para su uso dentro de un navegador web en solo unas pocas líneas de código...

var http = require('http');
var url = require('url');

http.createServer(function(request, response) {

    var parsed = url.parse(request.url, true);

    if (parsed.pathname == '/myFunction') {
        var result = myFunction(parsed.query.arg1, parsed.query.arg2);
        response.setHeader('Content-Type', 'application/json');
        response.setHeader('Access-Control-Allow-Origin', 'null');
        response.end(JSON.stringify(result));
    }

}).listen(8080, 'localhost');

... y luego llamarlo desde el navegador con

    $.getJSON('http://localhost:8080/myFunction?arg1=Hello&arg2=World', function(result) {
        console.log(result);
    });

Respuestas (2)

Bien, tuve que intentar codificar esto yo mismo.

https://github.com/Antony74/TransportManager/blob/master/sys/server/GenerateProxyApiSourceCode.js

Esto toma mi API central (que consta de las funciones 'selectSql', 'getIndices' y 'updateDatabase') y genera una API proxy a partir de ella (ver más abajo), que se escribe en un archivo y se sirve como un .js estático file sería, y es bastante bueno trabajar con él en el lado del cliente.

Definitivamente estoy preparado para otras respuestas más innovadoras, ya que todavía siento que probablemente "reinventé la rueda", y los comentarios sobre esta respuesta también son bienvenidos.

// auto-generated code

function createCoreApiProxy() {       

    function improveErrorMessage(numberStatus, textStatus) {  
        if (textStatus == 'timeout')                
            textStatus = 'Request timed out';       
        else if (numberStatus == 0)                 
            textStatus = 'No response from server'; 

        console.log(textStatus);                    
        return textStatus;                          
    }                                               

    return {                                        
        selectSql: function(query, startRecord, schemaLevel, fnDone) {
            $.ajax(
            {
                type: 'POST',
                url: 'http://localhost:8080/selectSql',
                timeout: 6000,
                data: JSON.stringify({
                    query: query,
                    startRecord: startRecord,
                    schemaLevel: schemaLevel,
                }, null, 4),
                success: function(returnedData) {
                    fnDone(JSON.parse(returnedData));
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    fnDone({Error:improveErrorMessage(jqXHR.status, textStatus)});
                }
            });
        },
        getIndices: function(fnDone) {
            $.ajax(
            {
                type: 'POST',
                url: 'http://localhost:8080/getIndices',
                timeout: 6000,
                data: JSON.stringify({
                }, null, 4),
                success: function(returnedData) {
                    fnDone(JSON.parse(returnedData));
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    fnDone({Error:improveErrorMessage(jqXHR.status, textStatus)});
                }
            });
        },
        updateDatabase: function(arrPostedData, fnDone) {
            $.ajax(
            {
                type: 'POST',
                url: 'http://localhost:8080/updateDatabase',
                timeout: 6000,
                data: JSON.stringify({
                    arrPostedData: arrPostedData,
                }, null, 4),
                success: function(returnedData) {
                    fnDone(JSON.parse(returnedData));
                },
                error: function(jqXHR, textStatus, errorThrown) {
                    fnDone({Error:improveErrorMessage(jqXHR.status, textStatus)});
                }
            });
        },
    };
}
Puede aceptar su propia respuesta. Si funciona para usted, aceptarlo ayudará a otros que lean la pregunta en el futuro.
@Mawg, gracias, pero sigo esperando una mejor respuesta. O, no sé, tal vez incluso descarte envolver un objeto como una mala idea y en su lugar envuelva una API REST (por ejemplo, devdactic.com/improving-rest-with-ngresource ).

Conceptualmente, puede separar su código en dos componentes: datos y funciones.

Aquí asumiré que los datos son estáticos y generados en el servidor:

// server
data = [1,2,3,4,5]

Ahora vamos a las funciones. Estos seguramente pueden ser compartidos entre el cliente. Lo más fácil que se me ocurre es enviar el código fuente de las funciones como cadenas y cargarlas en el cliente usando eval.

Primero defina algunas funciones en el servidor:

// server
allData = function () {
  return data
}
evens = function () {
  return data.filter(function(num){
    return num % 2 == 0
  })
}

Ahora un método en el servidor para enviar datos + funciones. Estoy usando un marco de manejo de solicitudes imaginario aquí; puedes traducirlo al que estás usando

  get('/dataAndFunctions', function(request){
    request.send(JSON.stringify({
      "data" => data,
      "sharedFunctions" => {
        "allData": allData.toString(),
        "evens": evens.toString()
      }
    }))
  })

Ahora en el cliente, solicitando y cargando los datos y funciones. Usando jQuery para este ejemplo

// client
$(function(){
  $.get("/dataAndFunctions", function(response){
    responseObject = JSON.parse(response)
    window.data = response.data
    Object.keys(responseObject.sharedFunctions).forEach(function(key){
      window[key] = eval(`(${sharedFunctions[key]})`)
    })
  })
})

Luego, tanto en el cliente como en el servidor, puede llamar evens()o allData(), siempre que los datos no cambien, se referirán a colecciones idénticas.

Esto puede parecer confuso en el código del cliente:

window[key] = eval(`(${sharedFunctions[key]})`)

Si a eval se le pasa una función como una cadena, debe estar entre ()paréntesis:

eval("(function(){return 1})")

El ejemplo que di usa cadenas de plantilla ES6 delimitadas por acentos graves y que contienen interpolación dentro${}