Jump to content

Andrea Magni

Members
  • Content Count

    146
  • Joined

  • Last visited

  • Days Won

    3

Posts posted by Andrea Magni


  1. Thanks again for spotting this bug. If you like your contribution to this project to be more visible, please set up a GitHub account (if you haven't yet) and add a pull request for this (or future) corrections. I can obviously apply your changes to MARS but your contribution will not be tracked by GitHub (even if I always try to mention the author in the commit message in this situations).

     

    I have merged your changes with this commit ( https://github.com/andrea-magni/MARS/commit/9f45d60e7adb65a36432840baf2cf3e526a8f518 ) and also fixed LastCmdSuccess method. I went for returning -1 in ResponseStatusCode when FLastResponse is not available (rather than zero).

     

    BTW I didn't know about Charles proxy ( https://www.charlesproxy.com/ ) I had some troubles configuring WireShark and Fiddler inside a VM but I can see Charles runs perfectly with no clue. Seems very nice so far, thanks!


  2. On 12/14/2018 at 2:40 PM, Bjørn Larsen said:

    At first I could not get it to work, but finally I figured out that I had to registered the writers in the correct order. 

    Otherwise the first writer would always be triggered.

    Apart from the registration order, you can define a higher affinity of your writer in order to supersede the standard ones.

    Just switch the value returned by the affinity function in the RegisterWriter call from TMARSMessageBodyRegistry.AFFINITY_MEDIUM to TMARSMessageBodyRegistry.AFFINITY_HIGH and your writer will have a priority over the standard record writer.

     

    I would then write the WriteTo implementation like this:

     

    procedure TScimPlusJSONWriter.WriteTo(const AValue: TValue;
      const AMediaType: TMediaType; AOutputStream: TStream;
      const AActivation: IMARSActivation);
    var
      LType: TRttiType;
    begin
      LType := AActivation.MethodReturnType;
      if not Assigned(LType) then
        Exit; // procedure?
    
      if LType.IsRecord or LType.IsDynamicArrayOfRecord then
        TMARSMessageBodyWriter.WriteWith<TRecordWriter>(AValue, AMediaType, AOutputStream, AActivation)
      else if LType.IsObjectOfType<TJSONValue> then
        TMARSMessageBodyWriter.WriteWith<TJSONValueWriter>(AValue, AMediaType, AOutputStream, AActivation);
    
      AActivation.Response.ContentEncoding := 'UTF-8';
    end;

     

    TMARSMessageBodyWriter.WriteWith<> is a new utility function I just introduced in the "develop" branch (MARS.Core.MessageBodyWriter unit, https://github.com/andrea-magni/MARS/commit/e818d7e261e7ef2b9a1c2c714adae459c5585c56#diff-97e07c61cdc46902bf53d808b8117104R157 ). You can simply instantiate the corresponding MessageBodyWriter class as we did before.

     

    Sincerely,

     

    • Like 1

  3. Hi @Stuart Clennett,

    it's a bug! 🙂 Thanks for spotting it.

    The TMARSFireDAC.InjectMacroValues and TMARSFireDAC.InjectParamValues implementations assume every TFDAdaptedDataSet actually has a Command (not nil). TFDMemTable do not have Command assigned.

     

    Workaround: if you do not need your memory table to be included as the result of Retrieve method, you can decorate it with the RESTExclude attribute:

     

        [RESTExclude]
        FDMemTable1: TFDMemTable;

     

    However, I just fixed this in the "develop" branch of MARS repository: https://github.com/andrea-magni/MARS/commit/b6299926671b00e75981c47a74375d9c51c529ca

    Another workaround would be to change your copy of TMARSFDDataModuleResource.Retrieve and change this line:

        if LDataSet is TFDAdaptedDataSet then

    to this:

        if (LDataSet is TFDAdaptedDataSet) and Assigned(TFDAdaptedDataSet(LDataset).Command) then

     

    The MARS.Data.FireDAC.DataModule has nothing special, it is provided as an example of use, you can copy it, change it... it is obviously the counter-part of a TMARSFDResource but there no direct deal with the specific class, just matter of the shape of its methods (types returned, GET/POST selection, ...).

     

    Thanks,

    Andrea

    • Like 1

  4. Hi @Bjørn Larsen,

    You have a number of possibilities and I would choose one way or another depending on the actual use case.

    If you need a single (or just a few) methods to produce application/scim+json content type, I would probably follow the way to let MARS produce standard JSON and then fix the ContentType/ContentEncoding.

    There's a handy way to do this through the Context attribute (MARS will inject a reference to the Response object you will be able to use it across all the methods you need):
     

      [Path('helloworld')]
      THelloWorldResource = class
      protected
        [Context] Response: TWebResponse; // add unit Web.HTTPApp to uses clause
      public
        [GET, Produces(TMediaType.APPLICATION_JSON)]
        function User1: TJSONObject;
      end;
    
    (...)
    
    function THelloWorldResource.User1: TJSONObject;
    begin
      Result := TJSONObject.Create;
      Result.WriteStringValue('id', TGUID.NewGuid.ToString);
    
      Response.ContentType := 'application/scim+json';
      Response.ContentEncoding := 'UTF-8';
    end;

     

    If you have several methods (in a single resource) that need to produce application/scim+json and you want to generalize this "response patching" one for all, you may take advantage of another MARS functionality: AfterInvoke code execution. It comes in a couple of variations, the first one will let you add a method (PatchResponse) and decorate it in order to have MARS execute it after each request to that resource:

     

      [Path('helloworld')]
      THelloWorldResource = class
      protected
        [Context] Response: TWebResponse;
      public
        [GET, Produces(TMediaType.APPLICATION_JSON)]
        function User1: TJSONObject;
    
        [AfterInvoke]
        procedure PatchResponse;
      end;
    
    (...)
    
    procedure THelloWorldResource.PatchResponse;
    begin
      Response.ContentType := 'application/scim+json';
      Response.ContentEncoding := 'UTF-8';
    end;
    
    function THelloWorldResource.User1: TJSONObject;
    begin
      Result := TJSONObject.Create;
      Result.WriteStringValue('id', TGUID.NewGuid.ToString);
    end;

    This way, your User1 (and other) method will not be polluted by the "response patching" and at the same time you have a single copy of the "response patching" code (DRY principle).

    Another AfterInvoke possibility is to define it at a more general level by calling TMARSActivation.RegisterAfterInvoke:

     

        TMARSActivation.RegisterAfterInvoke(
          procedure (const AActivation: IMARSActivation)
          begin
            if AActivation.ResourceInstance is THelloWorldResource then // or whatever other strategy
            begin
              AActivation.Response.ContentType := 'application/scim+json';
              AActivation.Response.ContentEncoding := 'UTF-8';
            end;
          end
        );

    This has the advantage to be very wide (possibly spanning over each MARS engine/application) and does not require the injection of the Response object at resource level. You can setup your own strategy to determine when it's the case to patch the response or not (i.e. use some custom attribute yourself to decorate resources/methods or check the current ContentType or whatever... the AActivation parameter will provide you a very detailed context over the current MARS activation including Request, Response, selected class, selected method, method return value and so on). 

     

    These are all "patching" options. There is another option available though: define a new media type.

    From the example you posted, I can't see if you are returning standard or custom types (i.e. TJSONObject instances or TYourClass instances/records). In both cases you can define a new MBW (MessageBodyWriter) for a new media type (application/scim+json) and have full control over Response generation.

    It's easy, I am attaching here a unit (MBW.ScimPlusJSON.pas) with a new MBW implementation for application/scim+json that will match any request with a Produces('application/scim+json') attribute and will act the same of the standard JSON MBW with the addition of setting the ContentEncoding of the Response. (ContenType will automatically be set by MARS).

    Just be sure to include the unit in the uses list of your resources unit or in the Server.Ignition unit.

    You resource then will look like this:

     

      [Path('helloworld')]
      THelloWorldResource = class
      protected
        [Context] Response: TWebResponse;
      public
        [GET, Produces(MEDIATYPE_APPLICATION_SCIM_PLUS_JSON)]
        function User1: TJSONObject;
      end;
    (...)
    function THelloWorldResource.User1: TJSONObject;
    begin
      Result := TJSONObject.Create;
      Result.WriteStringValue('id', TGUID.NewGuid.ToString);
    end;

     

    Hope this helps you to solve your problem and please let me know if something is unclear or you need anything else.

     

    Sincerely,

    Andrea
     

    MBW.ScimPlusJSON.pas

    • Thanks 1

  5. Thanks for figuring this out!

    I would say you should not have that file there (as it is not there in the repo https://github.com/andrea-magni/MARS/tree/master/Demos/MARSTemplate/bin ). You probably have it because you actually compiled MARSTemplate demo itself (did you?)...

    However it is obviously a fault of my strategy to rename files... I will try to improve (probably managing a list of files to rename and the expected final name and then have a much smarter copy strategy).

     

    Thanks again for taking the time to spot it and make it evident.

    I have added an issue on GitHub: https://github.com/andrea-magni/MARS/issues/52

     

    Sincerely,

    Andrea


  6. I am starting to think it's something related to Delphi version (Berlin).

    There are entries in quality.embarcadero.com about TNetHTTPClient and I found this: https://quality.embarcadero.com/browse/RSP-14301

    If the client is not sending cookies (and AuthEndorsement is set to Cookie), your situation is explained.

     

    The fact it works on my side may depend this may not affect all Berlin versions (I am using Delphi 10.1 Berlin Version 24.0.25048.9432, Berlin update 2 anniversary)...

     

    Do you have a chance to check with other Delphi versions? Or maybe someone else with Berlin may try to reproduce this?

     

    Thanks


  7. I've just released MARS version 1.3 ( https://github.com/andrea-magni/MARS/releases/tag/v1.3 … ). 
    New developments will take place in the new 'develop' branch and there's already a small new feature: basic IXMLDocument support in MBR and MBW.
     

    You can consider using the released version in order to avoid unnecessary noise due to new developments, that from now on will take place in a separate 'develop' branch.

     

    Sincerely,

    • Like 1
    • Thanks 3

  8. Thanks for you example, @Stuart Clennett

    This is what I get when I click the Test button:

     

    Login via TMARSNetClient successful
    Attempting GET with TMARSNetClient 
    {"Now":"09.22.22","Token.UserName: ":"admin","Token.Roles:":"standard,admin"}
    Login via TMARSClient successful
    Attempting GET with TMARSClient 
    {"Now":"09.22.22","Token.UserName: ":"admin","Token.Roles:":"standard,admin"}

     

    At this point I am starting to think there is something different in the Delphi version we are using (the authentication is actually endorsed through cookies (you may want to switch from cookies to Authorization header changing TMARSClient.AuthEndorsement property on both components and see what happens)).

    I am not aware of bugs in this area for Berlin but I may be missing something...

     

    Let me know please

     


  9. Hi, anybody had troubles performing a backup of a FB3 embedded database through the TIBBackup component?

     

    Firebird 3 uses fbclient.dll as client library also for embedded connections so it seems there's something wrong in the TIBBackup component when determining the client library to use (FireDAC.Phys.IBWrapper unit, TIBLib.GetLibraryInfo function. Given sLib var is always 'fbclient.dll' it misses it is a Firebird embedded connection).

     

    We tried to force fbclient.dll as vendor lib (giving a proper value to the DriverLink property of the TIBBackup instance, we also tried to set Embedded property of the driver link manually) but no way, it is always looking for fbembed.dll instead of the specified one (fbclient.dll).

    A quick and dirty workaround is to copy the DLL (fbclient) with the old name (fbembed) but I would like to avoid this, if possible.

     

    Any help would be appreciated. Maybe @Dmitry Arefiev may enlight us 😉

     

    Thanks in advance


  10. I really can't think what's going wrong...

    I recorded a short video, would you please try to step through it and see if something differs from what you are doing?

     

    Link: 

     

     

    Would you try a different output path for your project? (to see if there something to do with privileges on file system?) 

    If you like, we can have a remote session (Teamviewer or similar) to check it out together.

     

    Thanks for your patience


  11. 4 hours ago, Bjørn Larsen said:

    Hi Andrea, 

    Thank you for quick feedback and a perfect solution!

    Always nice to get first class feedback from the author 🙂

    Thank you for using MARS and please feel free to provide some feedback or ask questions (if needed) and I will be happy to help!

    Many features of my library came as users asked for this or that thing and I managed to implement it

     

    There's nothing better for a library author than seeing developers using it 🙂

     


  12. Hi, MARS core mechanisms, including authentication and authorization, take place inside TMARSActivation class.

    The default implementation is attribute-driven and it is hard to have such dynamic behavior through them.

     

    But you can easily inherit your own TMARSActivation descendant and tweak some of the internals.

    For example, in order to fullfil your request about bypassing RBAC, you can override the ReadAuthorizationInfo method (that determines if a resource/method is public, is forbidden to everybody or has a list of allowed roles defined through attributes) and fix the result at your will (for example making every request allowed or implementing some strategy of your own based on the request, the application or the specific resource invoked.... whatever).

     

    Here attached there is a unit Server.MyActivation that implements a total override of RBAC depending on a parameter of your engine (you can set this parameter in your ini file or by code in the Server.Ignition unit).

     

    Some code from the attachment, comment out CodeSite if you are not using it (it's free and you can install it from GetIt, I use it often).

      TMyActivation = class(TMARSActivation)
      protected
        procedure ReadAuthorizationInfo; override;
      end;
     
     procedure TMyActivation.ReadAuthorizationInfo;
    begin
      inherited;
      CodeSite.SendMsg('[STANDARD] Authent.: ' + BoolToStr(FAuthorizationInfo.NeedsAuthentication, True)
        + ' Author.: ' + BoolToStr(FAuthorizationInfo.NeedsAuthorization, True));
    
      if Engine.Parameters.ByName('DisableRBAC', False).AsBoolean then
      begin
        FAuthorizationInfo.PermitAll := True;
        FAuthorizationInfo.DenyAll := False;
        FAuthorizationInfo.AllowedRoles := [];
        CodeSite.SendMsg('[OVERRIDE] Authent.: ' + BoolToStr(FAuthorizationInfo.NeedsAuthentication, True)
          + ' Author.: ' + BoolToStr(FAuthorizationInfo.NeedsAuthorization, True));
      end;
    end;
    
    initialization
      TMARSActivation.CreateActivationFunc := function (const AEngine: TMARSEngine;
        const AApplication: TMARSApplication;
        const ARequest: TWebRequest; const AResponse: TWebResponse;
        const AURL: TMARSURL
      ): IMARSActivation
      begin
        Result := TMyActivation.Create(AEngine, AApplication, ARequest, AResponse, AURL);
      end;

     

    The initialization section is there to set the TMARSActivation.CreateActivationFunc variable, that acts like a poor-man factory for IMARSActivation.

    The library will call this function, if set, to instantiate the specific TMARSActivation descendant (TMyActivation in our case).

     

    You can set the parameter in the ini file of your server:

    [DefaultEngine]
    ThreadPoolSize=100
    DisableRBAC=True

     

    or setting it by code for example in the Server.Ignition file (after the FEngine.Parameters.LoadFromIniFile call):

        FEngine.Parameters.Values['DisableRBAC'] := True;

     

    Let me know if this solves your problem.

    Sincerely

    Server.MyActivation.pas


  13. Weird...

    Let's assume you checked out MARS in a certain folder "MARS_BASE" (mine is 'C:\Sviluppo\Librerie\MARS').

    You should have MARSCmd executable in MARS_BASE\Utils\Bin\Win32

    Once you ran it, it should detect MARS base folder (it's printed in the bottom left part of the MARSCmd window) and suggest you to clone MARSTemplate from MARS_BASE\Demos\MARSTemplate, when you click Next, you should be able to change the "Replace With" value with the name of your new project, then click Next again and you should see the Destination folder valued to something like MARS_BASE\Demos\NAME_OF_YOUR_PROJECT

     

    I can see from your bug report that you are using Berlin as I am in this exact moment so I actually don't understand what's going wrong.

     

    Let me know please

    Sincerely


  14. Hi, I just made a quick test and everything run smooth here.

    Please post your code and we can investigate together.

     

    Here ( https://www.dropbox.com/s/1ak4fdru3lrs2my/TestRolesProject.zip?dl=0 )  you can find a simple project, built with MARSCmd from MARSTemplate and then I added a simple helloworld/json method allowed only for 'admin' role.

    The [Context] Token: TMARSToken get valued correctly and the client (included in the same groupproject) at designtime seems to work properly (you can authenticate but setting Username and Password [beware: password is the hour of the day you are running the demo] on the TMARSClientToken component and perform a POST request, inspect the Roles property and see them correctly valued) and then you can execute the helloworld/json resource (again, from the IDE clicking "GET" in the ObjectInspector) and see results (that includes an echo of Token.Username on the serverside as well as Token.Roles).

     

    The test project I attached has been built with 10.1 Berlin and then I tested it with 10.3 Rio without a glitch.

     

    Let me know, I will be happy to help you out


  15. 1 hour ago, David Heffernan said:

    Doesn't this suggest that reducing the amount of heap allocation would lead to much bigger gains? 

    I am not actually an expert on this so low level topics but I am sure there is a big room for optimization in MARS.

    So far I always focused on functionalities and ease of use and there are a couple of spots I know I can easily optimize. It's on my todo list but not really a priority at this very moment.

    However, any help would be greatly appreciated, just in case somebody is willing to. 😉

×