Open tiagolr opened 9 years ago
Hi,
To be honest, the code is in there but I haven't used this style of remoting in production yet, so it is not thoroughly tested. Here are my notes so far:
Instead, you can use UFAsyncApi to wrap your sync API:
class SignupApi extends UFApi {
...
}
class AsyncSignupApi extends UFAsyncApi
http://api.ufront.net/ufront/api/UFAsyncApi.html
As I said, the code is in there, it compiles, but I'm not actually using it anywhere, so it is not thoroughly tested, and as you can see, not really documented either :P
There is a different way that is closer to the old school Haxe remoting, (with an async callback function as the final paremeter), which I can explain if you'd prefer that approach. I do use that one in production, but I'm hoping to move away from it because I prefer working with futures, and this will give more seamless client/server code sharing.
Jason
On Thu, Feb 26, 2015 at 9:37 AM, TiagoLr notifications@github.com wrote:
Hey, i have a server UFApi class like so:
class SignupApi extends UFApi { public function test():String { return "returned!"; } }
and i have a JS client that tries to call the server method like so:
static public function main() { var sign:SignupApi = new SignupApi(); trace("returned? " + sign.test()); }
The resulting trace is returned? null, I'm probably missing something as the return should be asynchronous, how can i fix this client? Thanks!
— Reply to this email directly or view it on GitHub https://github.com/ufront/ufront-mvc/issues/9.
Hey, thanks for the explanation.
To be honest i rather use normal ufront routes with ajax requests or configure standard haxe remoting for now, at least until such feature is working and has some reference, already experimenting with a lot of stuff at the moment and need to keep it simple.
Anyway great work so far with ufront, the routing and orm have been great to use so far!
By the way how do you mix haxe classic
remoting with ufront? I am not sure where should i put these:
var ctx = new haxe.remoting.Context().ctx.addObject("MyObjs", new MyObj());
haxe.remoting.HttpConnection.handleRequest(ctx);
i think those two are all thats needed for remoting to run on the server, then i have a snippet for the client side (which does not use ufront so far).
If i can get remoting + ufront working, maybe i can use it for a client backoffice I'm starting this week, instead of ajax requests to UF routes, which are not bad, but remoting may make it simpler for this case.
So I've extracted out the important bits for an app that uses this style or remoting. Hopefully it's a complete enough example that you can follow along, let me know how you go.
First of all, I have an "UFApiContext" that defines which "UFApi" classes are available for remoting. I put it in app/Api.hx
:
package app;
import ufront.api.UFApiContext;
import app.api.*;
class Api extends UFApiContext
{
public var setupApi:SetupApi; // These are all UFApi objects
public var signupApi:SignupApi;
public var loginApi:LoginApi;
}
Then in my Server.hx
file, I let it know to use this Api context for remoting:
var ufApp = new UfrontApplication({
indexController: app.Routes,
remotingApi: app.Api,
logFile: "log/app.log"
});
Then on my client, I have something like this:
import app.Api;
class Client
{
/** Launch the remoting API. So you call Client.remoting.loginApi.attemptLogin(...) */
// Note 1: `app.ApiClient` is automatically generated by the build macro in `app.Api`
// Note 2: This doesn't actually need to point to /remoting/, it just makes it easier to debug which HTTP connections are Haxe remoting...
public static var remoting = new app.ApiClient("/remoting/", processRemotingError);
public static function main() {
// You make remoting calls like this...
Client.remoting.loginAPI.getCurrentUser(function (currentUser) {
trace( 'You are logged in as $currentUser' );
});
}
// The error handler let's you know what kind of error was encountered.
// Here is a fairly verbose one I use in my app.
static function processRemotingError(error:RemotingError<Dynamic>)
{
var msg;
switch error {
case HttpError( callString, responseCode, responseData ):
msg = 'Remoting error during $callString';
msg += '\n Type: HTTP Error ($responseCode)';
msg += '\n Data: $responseData';
case ServerSideException( callString, e, stack ):
stack = ' '+stack.replace("\n","\n ");
msg = 'Remoting error during $callString';
msg += '\n Type: Server Side Exception';
msg += '\n Exception: $e';
msg += '\n Stack: $stack';
case ClientCallbackException( callString, e ):
msg = 'Remoting error during $callString';
msg += '\n Type: Exception thrown after remoting call, and during callback on client.';
msg += '\n Error: $e';
msg += '\n Note: If you compile with -debug, you can let this exception by handled by your browser\'s debugger';
case UnserializeFailed( callString, troubleLine, error ):
msg = 'Remoting error during $callString';
msg += '\n Type: Failed to unserialize response';
msg += '\n Trouble Line: $troubleLine';
msg += '\n Error: $error';
case NoRemotingResult( callString, responseData ):
msg = 'Remoting error during $callString';
msg += '\n Type: No "hxr" remoting response line in data';
msg += '\n Data: $responseData';
case ApiFailure( callString, err ):
msg = 'Remoting API call $callString returned a Failure';
msg += '\n Failure data: $err';
case UnknownException( e ):
msg = 'Unknown exception during remoting call $e';
msg += '\n The error was not wrapped in a RemotingError, unsure how it was found using HttpAsyncConnectionWithTraces. ';
msg += '\n Please consider filing a bug report.';
}
trace( msg );
}
}
Any examples of using remoting in a ClientJSApplication
?
@:route(POST, "/login")
public function processLogin(args:{username:String, password:String}):ActionResult
{
loginApi.attemptLogin(...);
return ViewResult({success: ...});
}
The above code is what I use if it is on the server. And my question is as below, what would be the code for the client?
// suppose the request is intercepted by a pushstate button and reach here at client-side
@:route(POST, "/login")
public function processLogin(args:{username:String, password:String}):ActionResult
{
asyncLoginApi.attemptLogin(...) >> function(user)
{
// what can I do here to trigger a page change?
}
// what to return?
}
What I exactly wanted to do:
Future
returned by step 2 and trigger a page change / UI change e.g. showing a error message (I don't know how to do this with ufront)Just figured a way, not sure if it is the best but I just write it down here:
Haxe:
// Client.hx
public static function main()
{
var app = new ClientJsApplication(...);
// register a function in the global namespace so that the button in html can call it
untyped js.Browser.window.onButtonClick = function()
{
// call the async api. When the future is done, use pushstate to trigger a page
// change, which will be handled (at client-side) by the corresponding UFControllers
asyncApi.doSometing().handle(function(outcome) PushState.push("/some/url"));
}
}
html page:
<!-- Put a button in page which, when clicked, will call the function defined above-->
<button onclick="onButtonClick()">Click me</button>
But that leads to another problem, how to get the api injected in the button callback?
Hi I have tried, without success in the moment to use an AsyncApi wrapping my api inside a ClienJsApplication: My attempt was the following in the controller:
@inject public var testApi:app.api.AsyncTestApi;
@:route("/jsonFile")
public function doJson() {
var result = "";
var surprise = testApi.gimmeJson().handle(function(outcome) {
//it never ever goes inside the handler
switch outcome {
case Success(jsonStr):
result = jsonStr;
var json:Json = Json.parse(result);
return new ViewResult(json);
case Failure(err):
return new ViewResult( { message: "error" }, "error.html");
}
});
return new EmptyResult(true);
}
The app just opens a json file and returns the content of it. With returning an EmptyResult I hopped the client just sits there waiting for the Surprise handler returning the ViewResult, but the handler is never ever called. I guess it is because the route method returns and the api no longer exists, so the handler is never called.. ?!
@MichaPooh see my pull request here: https://github.com/ufront/ufront-mvc/pull/15
Looks kinda cool. Is it working for you for the AsyncViewResult?
I can never make AsyncViewResult accept the outcome from the AsyncApi Event with your examples (ignoring that my templateEngine will complain) i get a bunch of compiler errrors.
It worked for me. Make sure you are supplying a Future<TemplateData>
to AsyncViewResult
's constructor
That's what I don't understand. The operator overload >> from Futur returns already the result wrapped in a Futur. So for example
var surpriseResult = testApi.gimmeJson() >> function (str) return Json.parse(str);
return new AsyncViewResult(surpriseResult);
should work - but i doesn't for me compiler complains with :
tink.core.Future<tink.core.Outcome<Unknown<0>, ufront.remoting.RemotingError<tink.core.Noise>>> should be tink.Future<ufront.view.TemplateData>
Sorry to bother you again - i just spent hours and hours with this and there is no other place to ask I guess.. can be I just don't understand enough the tink core lib...
try this
var surpriseResult = testApi.gimmeJson().map(function (str):TemplateData return Json.parse(str.sure()));
return new AsyncViewResult(surpriseResult);
Will map a Surprise<A, F> to a Surprise<B, F> with a A->B
According to tink_core doc, if your gimmeJson() is returning a Suprise
, the >>
operator will also give you a Suprise, which is not what AsyncViewResult
wanted. It wants a Future, not Suprise.
Thanks for your answers... The function returns just a json String but it's my (injected) instance of UFAsyncApi which wraps my result in a Surprise. Isn't it the same for you using an AsyncApi in a ClientJsApplication ? The UFAsyncApi doc states
- An API return type of
:Surprise<A,B>
will become:Surprise<A,RemotingError<B>>
.- An API return type of
:Future<T>
will become:Surprise<T,RemotingError<Dynamic>>
.- An API return type of
:Outcome<A,B>
will become:Surprise<A,RemotingError<B>>
.- An API return type of
:Void
will become:Surprise<Noise,RemotingError<Dynamic>>
.- An API return type of
:T
will become:Surprise<T,RemotingError<Dynamic>>
.
so whatever the implementation in the api returns it will always be a Surprise using the the AsyncApi,
Edit: I can make it work with
var result = testApi.gimmeJson().map(function (o) {
switch o {
case Success(jsonStr):return Json.parse(jsonStr);
case Failure(err): {
var data:TemplateData = { 'data': [ { 'label': 'error', 'value': 0 } ] };
return data;
}
}
});
return new AsyncViewResult(result);
nice to hear that!
Sorry for the silence on this guys - but I'm glad to see you made some progress.
As I've been discussing with @kevinresol over in #15 - we do already support Future<ActionResult>
as the return type of a controller action. All of this is still quite new and only lightly tested, but I'm looking to solidify support for ufront-client asap.
I'll try get a full complete example repo together before the WWX conference at the end of May, but in the mean time, the controller you want is something like:
@inject public var testApi:app.api.AsyncTestApi;
@:route("/jsonFile")
public function doJson() {
var futureViewResult = testApi.gimmeJson().map(function(outcome) {
switch outcome {
case Success(jsonStr):
var json:Dynamic = Json.parse(jsonStr);
return new ViewResult(json);
case Failure(err):
return new ViewResult( { message: "error" }, "error.html");
}
});
return futureViewResult;
}
If you are happy to use the default error handler, and you use tink_core's ">>" operator shortcut, you can make this smaller:
@inject public var testApi:app.api.AsyncTestApi;
@:route("/jsonFile")
public function doJson() {
return testApi.gimmeJson() >> function(jsonStr:String):ViewResult {
return new ViewResult( Json.parse(jsonStr) );
};
}
hello guys ... what's the state of this ? i see differences between docs and those codes. Btw it's not clear to me what goes to the client and to the server.
What is your problem now? The controller action function does allow you to return a future result now e.g. Future<ViewResult>
hello kevin Thx for the quick answer. the fact is i don't know what kind of code to follow. and what should rely on the server and on the client. i tried many things that won't work and i'm lost.
i think i need a good example ;)
what i understood is :
i need a Async api wrapping my api this async api is injected to my controller ( homeController ) with inject ( i don't get the need of the injection here ) on my Client.hx : this is where i would benefit of injection but can't inject anything ( nothing happens ) i don't know what to call from my Client. and i don't understand how i can separate pure server requests because of the isomorphic type of front.
if you got a gist with the 3 classes : the Api with the AsyncWrapper. the serverController the client with at least on call from client to the Api returning async data.
it would be very handy.
( by the way i struggled to make front work with those different versions of libs minject etc . i perhaps broke something) thanks
@postite I put up a simple Ufront api setup on Gist https://gist.github.com/MichaPau/237c85dac91590c3f437
Perhaps it's useful for you...
that's very kind of you @MichaPau ... perhaps because i use v2 of minject, sadly ufront doesn't want to inject my TestApi:
Internal Server Error
Failed to inject app.api.TestApi into app.api.AsyncTestApi
ufront.handler.MVCHandler.handleRequest:-1
Exception Stack:
Called from minject.point.MethodInjectionPoint.applyInjection Called from minject.Injector.injectInto Called from minject.Injector._instantiate Called from minject.provider.ClassProvider.getValue Called from minject.InjectorMapping.getValue Called from minject.Injector.getValueForType Called from minject.point.PropertyInjectionPoint.applyInjection Called from minject.Injector.injectInto Called from minject.Injector._instantiate Called from ufront.handler.MVCHandler.processRequest Called from ufront.handler.MVCHandler.handleRequest Called from ufront.app.HttpApplication.setContentDirectory@306
Hey, i have a server UFApi class like so:
and i have a JS client that tries to call the server method like so:
The resulting trace is
returned? null
, I'm probably missing something as the return should be asynchronous, how can i fix this client? Thanks!