You are not logged in.
Pages: 1
Here is a transcription of today's mails.
hello Doug,
Nice idea!
I wanted to do something like this.
There are already some feature request tickets for it.
See http://synopse.info/fossil/tktview?name=bd94c11ab1Some comments:
- why use {parametername} and not $parametername ?
if identifiers are plain alphanumerics, we may safely write 'customers/$customerid' or 'customers/$customerid/invoices/$invoicenumber'
- why use classes for DTO, and not record/dynamic arrays? Or why re-implement a TList by hand, as there is TCollection working great, even with pre-generic versions of Delphi?
- the best to add routing may be to add it in a way transversal to TSQLRestServerURIContext, in a new dedicated method - something like ExecuteSOAByInterfaceRouting
- RegisterUriTemplate() could be a good idea - and I would like to add method attributes for newer versions of Delphi: could be pretty convenient, in this case (I do not like much attributes for ORM, since it mixes database specifications and high level code, but it does make sense for routing).I will took a look at it.
Thanks a lot for sharing!
---
Arnaud Bouchez
http://synopse.infoLe 2014-06-10 09:06, Doug Billi a écrit :
> Arnaud,
>
> I'm working on a URI router that can map individual URIs to interfaces
> via templates.
>
> For example:
>
> Templates.Register('customers/{CustomerId}');
>
> Templates.Register('customers/{CustomerId}/invoices/{InvoiceNumber}');
>
>
> Templates.Register('invoices/{InvoiceNumber}');
>
> if Templates.FindMatch('customers/1234/invoices/5678',
> MatchedTemplate, Params) then
>
> Writeln('Match Found')
>
> else
>
> Writeln('NOT Found');
>
> The code is working (you can see it in URITEMPLATETEST.DPR).
>
> If there is a match on a template, the URI parameters will be
> extracted into JSON, for invocation of the method:
>
> [{"CustomerId":"1234"}, {"InvoiceNumber":"5678"}]
>
> I will also plan to extract parameters after the ?, but it would be
> necessary to extract them in the correct order, as is done in
> TSQLRestRoutingREST.ExecuteSOAByInterface(). But in order to do that,
> I'll need to associate the template with the correct interfaces.
>
> I was thinking of something like this:
>
> procedure RegisterUriTemplate(const aTemplate: RawUTF8;
> aImplementationClass: TInterfacedClass; aInterface: PTypeInfo;
> aMethod: TSQLURIMethod);
>
> It would be preferable to separate the templates for each service, so
> for the following services, something like this:
>
> // CustomersApi Service
>
> RegisterUriTemplate('customers/{CustomerId}', TCustomersApi,
> ICustomersApi.GetCustomer, mGET);
>
> RegisterUriTemplate('customers/{CustomerId}', TCustomersApi,
> ICustomersApi.CreateCustomer, mPOST);
>
> RegisterUriTemplate('customers/{CustomerId}', TCustomersApi,
> ICustomersApi.DeleteCustomer, mDELETE);
>
> RegisterUriTemplate('customers/{CustomerId}/invoices/{InvoiceNumber}',
> TCustomersApi, ICustomersApi.GetCustomerInvoice, mGET);
>
> // InvoicesApi Service
>
> RegisterUriTemplate('invoices/{InvoiceNumber}', TInvoicesApi,
> IInvoicesApi.GetInvoice, mGET);
>
> RegisterUriTemplate('invoices/{InvoiceNumber}', TInvoicesApi,
> IInvoicesApi.CreateInvoice, mPOST);
>
> RegisterUriTemplate('invoices', TInvoicesApi,
> IInvoicesApi.GetInvoices, mGET);
>
> However, I'm not sure how to best integrate it with mORMot, or even if
> this is the best approach.
>
> I've created a unit called RESTAPISERVERURICONTEXT.PAS, with a class
> that descends from TSQLRestServerURIContext to allow me to override
> URIDecodeSOAByInterface() and ExecuteSOAByInterface(), but I'm not
> sure where to do it. Perhaps this should be even be done earlier in
> the code?
>
> Of course, I want to retain the existing functionality for native
> Delphi clients in the same application, but the URL mapping is to
> provide an alternative way to access the methods from AJAX clients.
>
> The web service API I'm writing will need the URL conventions provided
> by the templates.
>
> So there are three issues:
>
> 1) Where to register the templates to map to the service interfaces.
>
> 2) Where to perform the lookup and execute the matched method.
>
> 3) Do this in a way that doesn't prevent a native Delphi client from
> calling the same services using the existing architecture.
>
> Do you have any suggestions for the correct way to integrate this
> template mapping feature?
>
> I've included two demo applications, one to test the URI templates
> (URITEMPLATETEST.DPR) and another (DEMOHTTPSERVER.DPR), which
> showcases how to implement multiple SOA services in a multi-threaded
> way (using sicPerThread). Everything was coded to make the demo easy
> to understand (it even includes pre-allocating the Data Access Layer
> class for each thread instance).
>
> Once I get the template code working, I think it would be a great
> example to include with mORMot to show how interface based services
> can be used in existing applications that haven't yet migrated to ORM.
> Honestly, I've searched high and low and there is nothing else out
> there that comes close to what mORMot provides. I want to see it
> become the de-facto web service framework for Delphi! At some point,
> it could implement attributes like WCF 4 to annotate the service
> methods with URI templates, but that's for another day. ;-)
>
> Of course, if you have any constructive feedback on how the demo
> works, please let me know.
>
> I could have posted this to the forum, but I thought it would be
> better to email you the sample code. However, I can always post there
> if you'd prefer.
>
> Thanks,
>
> Doug
Offline
But, perhaps we may use "convention over configuration" pattern.
For instance, instead of
GET 'customers'
GET 'customers/{CustomerId}'
GET 'customers/{CustomerId}/invoices/{InvoiceNumber}'
DELETE 'customers/{CustomerId}'
POST 'customers'
POST 'customers/{CustomerId}/invoices'
we may define a method as such:
ICustomers = interface
// for GET 'customers'
function get_: TServiceCustomAnswer;
// for GET 'customers/{CustomerId}'
function get_(CustomerID: integer): TServiceCustomAnswer;
// for GET 'customers/{CustomerId}/invoices/{InvoiceNumber}'
function get_invoices(CustomerID: integer; const InvoiceNumber: RawUTF8): TServiceCustomAnswer;
// for DELETE 'customers/{CustomerId}'
function delete_(CustomerID: integer): TServiceCustomAnswer;
// for POST 'customers'
function post_(const Body: RawByteString): TServiceCustomAnswer;
// for POST 'customers/{CustomerId}/invoices'
function post_invoices(CustomerID: integer; const Body: RawByteString): TServiceCustomAnswer;
end;
We may add some methods to TServiceCustomAnswer, to easily return any content.
We may allow to define a Ctxt: TSQLRestServerURIContext input parameter to let the method directly access all input content, in addition to Body: RawByteString.
Thanks to these patterns, you do not need to write any routing by hand.
We may add a dedicated method for custom routing - but would it be necessary?
BTW, for MVC implementation, I like very much the http://web2py.com/books/default/chapter/29/03/overview patterns.
This is much more than URI routing.
What do you think?
Offline
Nice! I like the "convention over configuration" idea.
Just so I understand, the following examples would work?
// for GET 'customers/{CustomerId}/invoices/{InvoiceNumber}/orders/{OrderNumber}'
function get_invoices_orders(CustomerID: integer; const InvoiceNumber, OrderNumber: RawUTF8): TServiceCustomAnswer;
// for GET 'customers/{CustomerId}/reports/orders/active'
function get_reports_orders_active(CustomerID: integer): TServiceCustomAnswer;
But this seems problematic:
// for GET 'customers/{CustomerId}/reports/orders/{OrderNumber}/details'
function get_reports_orders_details(CustomerID: integer; OrderNumber: RawUTF8): TServiceCustomAnswer;
Because the convention would expect:
'customers/{CustomerId}/reports/{OrderNumber}/orders/details'
instead of:
'customers/{CustomerId}/reports/orders/{OrderNumber}/details'
Which is what method attributes could solve.
And what about '?' parameters, as they will be necessary from some operations?
// for GET 'customers/{CustomerId}/reports/orders?$Filter=Price.gt.100&$OrderBy=OrderDate'
function get_reports_orders(CustomerID: integer; Filter, OrderBy: RawUTF8): TServiceCustomAnswer;
Of course, this would run into the same problems with the 'Filter' expected after the 'reports/'.
In any case, I think it would be important to have the method signature parameters after the '?' optional (if they aren't present in the method signature), because they are typically dynamic:
// for GET 'customers/{CustomerId}/reports/orders?$Filter=Price.gt.100&$OrderBy=OrderDate'
function get_reports_orders(CustomerID: integer): TServiceCustomAnswer;
var
Filter, OrderBy: RawUTF8;
begin
Filter := Ctxt.Input['Filter'];
OrderBy := Ctx.Input['OrderBy'];
end;
So if the parameters after the '?' are present in the method signature, then they are passed as arguments, otherwise they can be accessed via the Ctxt object (notwithstanding the problems mentioned above).
We may allow to define a Ctxt: TSQLRestServerURIContext input parameter to let the method directly access all input content, in addition to Body: RawByteString.
Having the option to specify a Ctxt parameter is a great idea, as above to be used for '?' parameters. I assume this would be an optional parameter?
With:
function get_reports_orders(Ctxt: TSQLRestServerURIContext; CustomerID: integer): TServiceCustomAnswer;
Without:
function get_reports_orders(CustomerID: integer): TServiceCustomAnswer;
Additional thoughts?
Offline
The solution is routing like Ruby on Rails does, using regular expressions:
Offline
For true MVC, routing should be IMHO automated for almost 95% of the process.
This is what our feature request proposed.
In short, the routing should better be hidden to the coder.
I think that routing is an implementation detail.
Only for consumption by or of third-party components, we may have to set a custom route.
I still can't understand why we have to set the route by hand in MVC.
In mORMot, I would like to do:
- Model = our existing ORM
- View = Mustache or AJAX / https://angularjs.org
- Controller = interface-based methods (implemented in Delphi or JavaScript)
The routing will take place between the View and the Controller, fully by convention, without any link to REST process itself.
IMHO we should not need to define CRUD operations of routing, just connect View and Controller in code, then the routing would be done automagically.
Offline
I still prefer the automated approach, as this is for developer consumption anyway.
I also think use of regular expressions is overkill and slow. I initially tried that approach, but it's actually far more complicated than at first it seems. URI templates are really not that complicated, as they essentially identify which URI path segments contain variables. The parser I wrote handles all of these cases and is very fast.
Regarding Arnaud's proposal, to avoid the ambiguity with the following case:
GET 'customers/{CustomerId}/reports/orders/{OrderNumber}/details'
function get_reports_orders_details(CustomerID: integer; OrderNumber: RawUTF8): TServiceCustomAnswer;
Camel-case could be required to delineate segments that do not contain parameters:
GET 'customers/{CustomerId}/reports/orders/{OrderNumber}/details'
function get_reportsOrders_details(CustomerID: integer; OrderNumber: RawUTF8): TServiceCustomAnswer;
Another approach could be to use a predetermined numeric value (e.g., '0' - zero) to denote the parameters:
GET 'customers/{CustomerId}/reports/orders/{OrderNumber}/details'
function Get_0_Reports_Orders_0_Details(CustomerID: integer; OrderNumber: RawUTF8): TServiceCustomAnswer;
Which is IMHO *much* more readable.
Quite frankly, I want to spend as little time with configuration as possible, because I have too much work to do already.
So an implementation that just works 'automagically' as Arnaud says is a huge plus for me.
Last edited by avista (2014-06-11 02:47:08)
Offline
Python's Flask has a dedicated example showing how to access the web server using jQuery, I WISH mORMot can provide similar easiness and similar example:
https://github.com/mitsuhiko/flask/blob … index.html
<script type=text/javascript>
$(function() {
var submit_form = function(e) {
$.getJSON($SCRIPT_ROOT + '/_add_numbers', {
a: $('input[name="a"]').val(),
b: $('input[name="b"]').val()
}, function(data) {
$('#result').text(data.result);
$('input[name=a]').focus().select();
});
return false;
};
});
</script>
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
Great! I haven't used html front end yet, so didn't notice that!
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
Further to the 'convention over configuration' discussion, the method parameters could be used to determine if a URI segment is a variable or not:
GET 'customers/{CustomerId}/reports/orders/{OrderNumber}/details'
function Get_CustomerId_Reports_Orders_OrderNumber_Details(CustomerID: integer; OrderNumber: RawUTF8): TServiceCustomAnswer;
Example: 'customers/1234/reports/orders/5678/details'
So the'_' denotes a '/', and if the text between the segments matches a parameter as declared in the method, it would get parsed out.
Having said this, I think there is some argument to be made for using attributes, because they decouple the method/parameter name(s) from the URL used to call it. That could be useful to prevent changes to the method from causing unexpected URI routing errors, and provides a lot of flexibility to present the URI to the consumer in any way they expect, without tying it explicitly to the implementation method name or parameters.
[WebGet('customers/{aCustomerId}/reports/orders/{aOrderNumber}/details?Filter={aDetailFilter}')]
function GetCustomerOrderDetails(aCustomerId: integer; aOrderNumber, aDetailFilter: RawUTF8): TServiceCustomAnswer;
As discussed previously, having access to the Context (if present as a parameter) would cover any case. Also, if a '?*' was at the end, the system would not expect the options to be passed in a method parameter:
[WebGet('customers/{aCustomerId}/reports/orders/{aOrderNumber}/details?*')]
function GetCustomerOrderDetails(Ctxt: TSQLRestServerURIContext; aCustomerId: integer; aOrderNumber: RawUTF8): TServiceCustomAnswer;
That seems to cover all the bases, but I'm still interested to know more about how the MVC pattern that was proposed would work.
Thoughts? Comments?
Last edited by avista (2014-06-22 21:09:56)
Offline
AFAIK the context is already available, as part of ServiceContext.Request threadvar.
Yes, we may indeed need to support convention over configuration for most simple cases, then attributes or method-driven registration for other URI patterns.
Offline
When a service class instance is created in sicPerThread mode, is it safe to cache the context for subsequent use?
aServer.ServiceRegister(TMyServiceApi, [TypeInfo(IMyServiceApi)], sicPerThread);
...
constructor TMyServiceApi.Create;
begin
inherited;
fContext := ServiceContext; <-----------------
end;
procedure TMyServiceApi.GetCustomer(out aCustomer: TCustomer);
var
Param1: RawUTF8;
begin
Param1 := fContext.Input['P1']; <--------------
...
end;
Or should the ServiceContext threadvar always be referenced directly when used?
Thanks
Last edited by avista (2014-07-08 04:19:54)
Offline
Hi guys, I didn't dig deep into your discussions here, so please forgive me if what I'm posting here is irrelevant. So today I came across a web service that allows you creating a web API by uploading an Excel spreadsheet plus some point-and-click operations, very simple and cool, I'm wondering if this will give Arnaud some inspiration.
For example, I uploaded an spreadsheet like this:
Order No Order Date Product Id License Key
1 Today p01 abcd-efg
2 2014/7/15 p02 hijk-lmno
3 2014/7/16 p03 erty-oiuy
Clicked OK, and I got this https://sheetlabs.com/#/services/doc/INNO/test#examples
Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.
Offline
When a service class instance is created in sicPerThread mode, is it safe to cache the context for subsequent use?
I suppose it is safe.
But the overhead of using a local variable, instead of a property, is IMHO negligeable.
And if you change later the mode from sicPerThread to another, your code won't break.
So, it sounds safer to me to use a local variable.
today I came across a web service that allows you creating a web API by uploading an Excel spreadsheet plus some point-and-click operations
It is in fact some nice REST-like requests.
The URIs sound not very deep, but concept is interesting.
Offline
I'd like to use the routing as you mentioned above:
ICustomers = interface
// for GET 'customers'
function get_: TServiceCustomAnswer;
// for GET 'customers/{CustomerId}'
function get_(CustomerID: integer): TServiceCustomAnswer;
....
but it's not work for me. Can you please provide a real REST routing with interface approach?
Offline
I add most of IANA HTTP methods to TSQLURIMethod
We need it to implement WebDAV/CalDAV protocols.
See [67bc72c07e]
Offline
Are there any news about URI routing? I want something like you described
GET 'customers'
GET 'customers/{CustomerId}'
POST 'customers'
DELETE 'customers/{CustomerId}'
PUT 'customers'
GET 'customers/{CustomerId}/invoices/{InvoiceNumber}'
POST 'customers/{CustomerId}/invoices'
DELETE 'customers/{CustomerId}/invoices/{InvoiceNumber}'
and so on.
Is there any way to do it right now?
Offline
Thanks a lot. I'll use it. But are you planning to implement ideas from this topic?
Offline
I would also like to implement a REST API for our Invoice/Orders/etc.. Can someone please post a code example how to implement thru method based services the processing of:
GET 'customers'
GET 'customers/{CustomerId}'
POST 'customers'
DELETE 'customers/{CustomerId}'
PUT 'customers'
GET 'customers/{CustomerId}/invoices/{InvoiceNumber}'
POST 'customers/{CustomerId}/invoices'
DELETE 'customers/{CustomerId}/invoices/{InvoiceNumber}'
Thank you.
Offline
Please write a snippet of code because I didn't find in samples.
Offline
Hello!
A very interesting topic. Can write a snippet or describe here how to use the examples? It is not clear where then used interface ICustomers. Is it possible to use the URI routing in my MVC application to mORMot. Sorry if asking stupid questions but I'm new.
I have not seen this implementation in the documentation and examples. Thanks in advance!
Offline
Is it currently possible to do for example
GET 'customers/{CustomerId}/invoices/{InvoiceNumber}'
or
POST /root/Calculator/Add/1/2
using interface based services?
Last edited by squirrel (2022-05-11 09:06:37)
Offline
Not possible in interface based service.
Workaround will be to create method based service, manually parse URI after root and call interface in method implementation.
Offline
How would I then find and execute the correct function on that interface with the correct parameters? I assume I'll have to search for the methodindex from the interfacefactory and then step through and assign the declared parameters?
Have someone done something like this before?
Since ExecuteCommand is private, how would the function be called after the parameters are assigned?
Last edited by squirrel (2022-05-11 12:15:45)
Offline
Do you control client? Interface based is fine but you have to pass params as arguments to function so it's a bit different than method based where you pass it via URL/URI
Offline
I need to temporarily put something in place of a Datasnap server that third parties integrated to. So what is important, is that I maintain exactly the same uri formats. Unfortunately that means that third party clients over which I have no control, need to be able to make POST requests such as /root/Calculator/Add/1/2 which I need to translate to the registered interface functions, ie to ICalculator.Add(1, 2) and execute it.
Offline
How would I then find and execute the correct function on that interface with the correct parameters? I assume I'll have to search for the methodindex from the interfacefactory and then step through and assign the declared parameters?
Have someone done something like this before?Since ExecuteCommand is private, how would the function be called after the parameters are assigned?
AFAIK it's already documented on how to call interface based services on server side : https://synopse.info/files/html/Synopse … #TITLE_421
Offline
I have done something similar to the documentation, but it looks as if all requests are then executed sequentially. I created a method that executes the functions like this
if Services['Calculator'].Get(svr) then
if SameText(req.FunctionName, 'Add') then
Ctxt.Returns(svr.Add(1, 2))
To come to that conclusion, I did something like this in Add:
...
StartTime := now;
Sleep(5000);
StopTime := now;
Result := '{"function": "Add", "StartTime": "' + DateTimeToStr(StartTime) + '", "StopTime": "' + DateTimeToStr(StopTime) + '"}';
..
When I open 2 browsers and call the above method at almost the same time, the results indicate that they are executed one after the other:
browser1: {"function": "Add", "StartTime": "2022/05/12 11:26:56", "StopTime": "2022/05/12 11:27:01"}
browser2: {"function": "Add", "StartTime": "2022/05/12 11:27:01", "StopTime": "2022/05/12 11:27:06"}
Is this the expected behaviour, or am I doing something wrong? The interface was declared with sicShared, but the same effect is seen when using sicPerThread or sicSingle .
Offline
I don't understand what you did on server side.
Method-based services are never serialized.
Interface-based services are not serialized by default.
Edit:
I just made some tests, and two FireFox instances actually do serialize the connections, whereas plain curl do not. I don't know why.
So the serialization seems to be on the client side.
$ curl http://localhost:11111/root/example/delay
{"result":["start=2022-05-12 10:29:32 stop=2022-05-12 10:29:37"]}
$ curl http://localhost:11111/root/example/delay
{"result":["start=2022-05-12 10:29:33 stop=2022-05-12 10:29:38"]}
Offline
32 threads
Maybe this is a browser issue and not a framework issue. It happens when I test using two tabs of the same browser, but not when using two different browsers at the same time, such as chrome and firefox.
Offline
Just a wild guess,
I assume your browser test is using a GET request, you should make a POST request instead. From the browsers perspective you're trying to access a cacheable resource, there's no reason to send a second request for the same resource if the first one is still waiting for server response. Either POST request or append a different parameter(Y) to the GET request for each X /root/example/delay?X=Y.
Offline
Yes, that is probably it. Maybe its a browser issue. I wont worry too much about that now.
Offline
Pages: 1