Jump to content

Stuart Clennett

Members
  • Content Count

    44
  • Joined

  • Last visited

  • Days Won

    1

Posts posted by Stuart Clennett


  1. So I'm back to this.  I'm currently thinking of using Data-URLs to get the base64 image data and embed direcly into the Angular client app.  (ref: https://css-tricks.com/data-uris/ & https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)

     

    Note that with the above TBytes solution I seem to get the response "[255, 0, 255, 0, 45, 56, 67 ..... ]"  as text rather than raw bytes.  The end point has the response type set as 'image/jpeg' 

     

     


  2. Hi,

     

    Not sure if I'm on the right track here.  I have a legacy database that has some images stored in blob fields in the database (I know).  I'd like to be able to expose these images directly from the REST API, e.g. localhost:8080/rest/default/images/{id} 

     

    I guess I have to set the Produces attribute to `image/jpeg` .. but what data type should I return?  I've tried TBytes but although I get the bytes returned in Postman - web browsers don't load the image. 

     

    Thanks


  3. @alejandro

     

    The demo does not save the file at all.  You are required to implement that.  However it is quite simple.

     

    For example, in the `StoreDataAndFile` method in `Server.Resources.pas` you could use a memory stream to access the Bytes and save to a file

     

        if SameText(LParam.FieldName, 'image') and LParam.IsFile then
        begin
          Result.FileSize := Length(LParam.AsFile.Bytes);
          Result.FileName := LParam.AsFile.FileName;
    
          // This will save the file
          lStream := TMemoryStream.Create;
          try
            lStream.Write(LParam.AsFile.Bytes, Length(LParam.AsFile.Bytes));
            lStream.SaveToFile(TPath.Combine(C_RootPath, TPath.GetFileName(LParam.AsFile.FileName)));
          finally
            lStream.free;
          end;
    
        end

    In this instance,  `C_RootPath` is just a folder on the server where uploaded files should be saved, but you could make this dynamic based on user info in the Token for example. 

     

    Hope this helps

    • Like 1
    • Thanks 2

  4. Hi all,

     

    I've upgraded to MARS 1.4 and I'm hitting this compiler error on all my servers - including the MARSTemplate servers:

     

    [dcc32 Error] Server.Ignition.pas(86): E2010 Incompatible types: 'TBeforeHandleRequestProc' and 'Procedure'

     

    This is in response to the following:

        FEngine.BeforeHandleRequest :=
          function (const AEngine: TMARSEngine;
            const AURL: TMARSURL; const ARequest: TWebRequest; const AResponse: TWebResponse;
            var Handled: Boolean
          ): Boolean
          begin
            Result := True;
    	    {  code snip }
           end;

    which is in class constructor TServerEngine.CreateEngine.

     

    I'm not that great with anonymous methods & can't figure this one out.

     

    Thanks in advance,

     

     


  5. Hello Andrea,

     

    That's excellent, thank you very much for your detailed answer.  I agree with you that the Connection attribute seems a much cleaner implementation and I will have a go at refactoring my project. 

     

    Thanks again,

     

    Best wishes,

    Stuart

    • Like 1

  6. Hi,

     

    Is there anyway I can use a TFDQuery owned by a TDataModule with the resource result type TFDDataset (produces TMediaType.Application_JSON) ? 

     

    At present MARS adds the FDQuery to the Activation context, so it tries to free it & it's already freed when the Datamodule is freed (as part of the resource cleanup) in the TMARSActivation.Invoke, e.g.

     

    function TAssessmentResource.GetAssessment(const id: string): TFDDataSet;
    begin
      // result := FData.qryAssessmentProperties;    <-- results in Invalid Pointer Operation (FData is private DataModule)
      result := FD.CreateQuery(SQL_ASSESSMENT_PROPERTIES, nil, True, 'assessmentProperties');
      TFDQuery(result).Params[0].AsString := id;
    end;

    The cause is as below:

    procedure TMARSActivation.Invoke;
    // [..snip..]
    begin
      try
      	// [ ... invoke code ... ]
      finally
        // teardown phase
        FTeardownTime := TStopwatch.StartNew;
        if Assigned(FResourceInstance) then
          FResourceInstance.Free;  //<-- datamodule & TFQuery freed here
        FreeContext;               //<-- TFDQuery is in the current context so attempts to free here = Invalid Pointer Operation
        FTeardownTime.Stop;
      end;

     

    I'd rather not have to create my own JSON stream, but I'd like to have my DB logic encapsulated in a data module.

     

    Thanks


    Stuart

     


  7. Ah, figured it out.

        FEngine.BeforeHandleRequest :=
            function (const AEngine: TMARSEngine; const AURL: TMARSURL; const ARequest: TWebRequest; const AResponse: TWebResponse; var Handled: Boolean): Boolean
            var
              App: TMARSApplication;
            begin
              Result := True;
    
              // skip favicon requests (browser) ... [snip]
       
              // Handle CORS and PreFlight ... [snip]
    
              // Get the default connection name based on the host URL
              if (pos('localhost', AUrl.URL) > 0) or (pos('192.168.', aUrl.URL) > 0) then
                cConnectionDefname := 'LOCAL_DB'
              else
                cConnectionDefName := 'MAIN_DB'; 
    
              // apply to every application
              for App in AEngine.Applications.Values do
                App.Parameters.Values['FireDAC.ConnectionDefName'] := cConnectionDefName;
    
            end;

    So I get the default connection def name based on the URL.   

     

    Then set the correct parameter for each hosted application.  (My previous mistake was setting the `FireDAC.ConnectionDefName` parameter at the Engine level)

     

    If anyone can think of a better or more elegant way, please let me know.

     

    Thanks

     

     


  8. Thanks Lars.

     

    I'm making a lot of use of the TMARSFireDAC instance (usually called FD) that gets injected into the resource classes which is great for simple data -> api tasks.  I only rarely use a separate datamodule with an FDConnection component.  When I do, I can just set the FDConnection1.ConnectionDefName to whatever is in the INI file in DataModuleCreate.

     

    I can also set FD.ConnectionDefName, but the thing I'm most struggling with is where

     

    I can't see any way to set the default connection name in BeforeHandleRequest.  The RegisterBeforeInvoke has access to the Application.Parameters (where I think I can set ['FireDAC.ConnectionDefName']) but it's called after the TMARSFireDAC instance is already created and injected. 

     

    😕

     

     


  9. Hi,

     

    I'm looking at the TMARSFDDataModuleResource and I see that it has protected members `FD` (TMarsFireDAC) and `URL` (TMARSURL).  In my descendant class both of these are `nil` references.  Is there a way to use this?

     

    Reason is I'm trying to switch DB connections based on the server's own host URL.  E.g. if it's accessed via localhost or 192.168.x.x then use the LOCAL_DB connection def, otherwise use PROD_DB

     

    Edit: Which also begs the question, where is the best place to call `FD.ConnectionDefName := MyConnectionDefName;` ?

     

     

    Thanks


    Stuart


  10. Hi Andrea,

     

    Thanks very much.  Confirmed working with Postman and the delphi demo client.  

     

    Still can't get my HTML to work though - it just uploads the file name not the file contents.  Looking into this now.

     

    *Edit* My bad forgot the enctype="multipart/form-data" attribute from the form tag (which is ironic given that I was testing multipart/form-data, ha ha)

     

    Kind regards,

     

    Stuart

    • Like 1

  11. Hi Andrea,

     

    I want to implement this in my server, so I started with your demo above and tried to use Postman to test the API.    When I run Postman against your [..]/helloworld/2 endpoint I get the following back:

    {
        "JSONData": "{\"name\": \"Stuart\"}",
        "PersonName": "Stuart",
        "FileName": "",
        "FileSize": 0
    }

    This is my PostMan Setup:  https://imgur.com/td0fDJ4  (Sorry, can't find anyway to export the Postman config for that request).

     

    The json data is picked up okay, but not the file data.  I'm sending an Excel file in this case, but I've tried pure binary files like JPGs too.

     

    Can you see what I'm doing wrong with Postman? 

     

     

    Thanks


  12. Hi,

     

    I'm trying to save a JWT token to a file between program runs.  I have a console app that runs on a short schedule and I want to prevent having to authenticate each time.

     

    I am using MarsClientToken SaveToStream / LoadFromStream to get the json token data into and out of a temp file.   The contents of the temp file look fine & contain the json I would expect, i.e. the "Token" and each token property (the expired date is always +24hrs )

     

    When the token data is loaded from the file, IsVerified = True, isExpired = false, and Authenticated = True, but I am getting 403 forbidden when I make my api calls.    Logging in with username/password works fine of course.

     

    Is there something I am missing ? 

     

    Thanks

     


  13. Hi,

     

    I've tried adding a second app to my MARS rest engine (in order to support my api's version 2) as follows:

     

    class constructor TServerEngine.CreateEngine;
    var
      MyApp, MyApp2 : TMARSApplication;
    begin
      FEngine := TMARSEngine.Create;
      try
        // Engine configuration
        FEngine.Parameters.LoadFromIniFile;
    
        // Application configuration
        MyApp := FEngine.AddApplication('IBCSApp', '/ibcs', [ 'Server.Resources.*']);
        // Second Application (version 2 of the API)
        MyApp2 := FEngine.AddApplication('IBCSApp2', '/ibcs2', [ 'server2.Resources.*']);
        // [....]
    End;

    And I've created a server2.Resources.pas file into which I've added a simple "/healthcheck" resource that returns the server time as a string.  I've also added this line

     

    initialization
      TMARSResourceRegistry.Instance.RegisterResource<THealthCheck>;

    But when the server runs, IBCSApp2 is shown in the list of engines, but it does not contain the /healthcheck resource. 

     

    I should mention that the original app (IBCSApp) also has the same resource name (/healthcheck) implemented by a different class.

     

    I guess I am missing something?

     

    Thanks

    Stuart

     

×