Jump to content

Andrea Magni

Members
  • Content Count

    68
  • Joined

  • Last visited

Community Reputation

28 Excellent

2 Followers

About Andrea Magni

  • Birthday 05/24/1982

Technical Information

  • Delphi-Version
    Delphi 10.3 Rio

Recent Profile Visitors

258 profile views
  1. Andrea Magni

    CORS.Enabled?

    You are probably missing to actually reply to preflight requests (request made with OPTIONS verb). Look here: https://github.com/andrea-magni/MARS/blob/master/Demos/MARSTemplate/Server.Ignition.pas A commented example for FEngine.BeforeHandleRequest is provided and there is a (furtherly) commented section to address preflight requests: // Handle CORS and PreFlight if SameText(ARequest.Method, 'OPTIONS') then begin Handled := True; Result := False; end; This is a very minimalistic implementation (basically responding 200 OK to every preflight request) but it's needed for CORS to properly work. HTH, Andrea
  2. Andrea Magni

    CORS.Enabled?

    Hi, @Rafael Mattos The first version is the correct one (it's a Boolean value), if you want to set that parameter via code. Consider you can easily set CORS options in the default ini file associated with your server application. Simply add a line under the corresponding engine's section: [DefaultEngine] CORS.Enabled=true You can set these additional options (here listed with their default values), if you need it: CORS.Origin=* CORS.Methods=HEAD,GET,PUT,POST,DELETE,OPTIONS CORS.Headers=X-Requested-With,Content-Type,Authorization Sincerely, Andrea
  3. Andrea Magni

    MarsToken.SaveToFile / LoadFromFile

    No problem at all! I am investigating if it would be possible to force cookies of the client when loading the token from file/stream. IIRC it was not easy to do with one of the two http client libraries currently available (Indy and NetHttpClient). Sincerely, Andrea
  4. Andrea Magni

    MarsToken.SaveToFile / LoadFromFile

    Hi, hard to say without looking at the code but I guess it is because of the TMARSClient.AuthEndorsement value. By default MARS uses cookies to endorse the token in the requests. But if you are loading the token from file, it is quite probable the client does not have the cookie set. MARS actually supports two ways to endorse the token in the request: cookie and the Authorization header. If you set the client AuthEndorsement property to AuthorizationBearer, does it works? I've built a simple example (server and client) as an example: https://www.dropbox.com/s/zu0ovdhj1g88md9/MyConsole.zip?dl=0 Just run the MyConsoleServerApplication and then the MyConsole. Let me know if everything is fine. Sincerely, Andrea
  5. Hi all, @Rafael Mattos, I am fine thanks! I am relatively silent these weeks as I am a bit busier than usual and time is always lacking. Sorry about that. MARS collects result values of your methods and use them to properly serialize them and send them to the client. The result value will be freed by MARS at the end of the "Activation" (the handling of an incoming request down to the sending of the serialized result to the client). If you want to prevent MARS from freeing the instance you are returning, you can decorate your method with the IsReference attribute ( defined in https://github.com/andrea-magni/MARS/blob/master/Source/MARS.Core.Attributes.pas ). [GET, PermitAll, IsReference] function GetProduct([FormParam] ClientId: Integer):TJSONArray; Hint: there was a typo in your PermiAll attribute (PermiteAll) and you can omit the PermitAll attribute as it is the default behavior. Let me know if this solves your problem. Sincerely,
  6. Andrea Magni

    Handelling temprory files

    Hi @kitesoft2015@gmail.com, you can have a method, in your resource, marked with AfterInvoke attribute. This method will be called after the actual response has been sent to the client so I think it would be a good moment to delete the file. You may store the temp file name in a field of your resource. In theory for temporary files you may ask the operating system to provide a proper file name (and path) so that the OS knows it can clean those files when needed. I guess your question comes because you actually don't want that file to stay on the file system longer than needed. Also consider you may serve a stream (without passing from the disk file), if you can. Sincerely, Andrea
  7. Andrea Magni

    Hands-On Design Patterns with Delphi

    I am not sure to be there, sorry. However if I'd make it, I may consider buying some copies from you to make some gifts for my best customers 😉
  8. Andrea Magni

    Hands-On Design Patterns with Delphi

    Congrats! I just ordered my paperback copy (you'll sign it one day 😉 ) SIncerely
  9. Andrea Magni

    Error in LAction.Target := nil

    Hi @Ruslan, if you are freeing (closing) the frame (frameinfo) within an event handler of an UI element you may need to wait some amount of time in order to let the UI finish its activities related to the event itself (i.e. if it's a button, there might be some animations going on and/or similar things). Sincerely, Andrea
  10. You are welcome. We may consider adding some kind of (light) validation to the serialization/deserialization mechanisms. I like to keep MARS lightweight but something like "this field is required" could be safe to implement.
  11. Hi Marco, thanks for the kind words about MARS, happy to see you using it. There are a number of topics involved here as you surely know. Data validation and how to send results to the client have a lot of implications (involving your API design).I will try to expose some capabilities MARS has that may be useful when dealing with this topics but then it's up to you to choose an approach suitable for you on the server side to implement and for the client side to properly (conveniently) handle. Scenario: We defined a TMyRecord type as follow: TMyRecord = record Name: string; Surname: string; Age: Integer; DateOfBirth: TDate; function ToString: string; end; function TMyRecord.ToString: string; begin Result := string.join(' ', [Name, SurName, DateToStr(DateOfBirth), Age.ToString]); end; and we also have defined a REST method, expecting the client to send a JSON object representing our record: [POST, Consumes(TMediaType.APPLICATION_JSON), Produces(TMediaType.TEXT_PLAIN)] function SayHelloWorld([BodyParam] ARecord: TMyRecord): string; *** First approach: hooking into de-serialization mechanism *** MARS JSON-to-record built in support has a couple of goodies like: - you can add an additional "_AssignedValues: TArray<string>" field to your record and MARS will fill that array with the names of the fields of the record actually filled from the provided JSON object (so you can determine if the Age field is zero because it was not provided by the client or has been provided with the zero value) - you can add your record a function with the following prototype function ToRecordFilter(const AField: TRttiField; const AObj: TJSONObject): Boolean; MARS JSON-to-record mapping mechanism will call this function for each field of the record so you have a chance to transform or validate input data there. Example: [JSONName('')] // no JSON serialization/deserialization ValidationErrors: TArray<string>; function TMyRecord.ToRecordFilter(const AField: TRttiField; const AObj: TJSONObject): Boolean; begin Result := True; // no filtering if SameText(AField.Name, 'Age') then begin if AObj.ReadIntegerValue('Age') < 18 then ValidationErrors := ValidationErrors + ['Age: You must be 18+ to enter']; end else if SameText(AField.Name, 'DateOfBirth') then begin var LDateOfBirth := AObj.ReadDateTimeValue('DateOfBirth', -1); if LDateOfBirth > Now then ValidationErrors := ValidationErrors + ['DateOfBirth: Value cannot be in the future'] else if LDateOfBirth = -1 then ValidationErrors := ValidationErrors + ['DateOfBirth: Value is mandatory']; end; end; You can also return False and manually assign the record field value (using some defaults or transforming data from the AObj JSON object). So you'll find your ValidationErrors field filled with data you can check whenever you need to know if the record is valid or not. I would add two methods to the record: function TMyRecord.IsValid: Boolean; begin Result := Length(ValidationErrors) = 0; end; procedure TMyRecord.Validate; begin if not IsValid then raise Exception.Create(string.join(sLineBreak, ValidationErrors)); end; You REST method would be implemented this way: function THelloWorldResource.SayHelloWorld([BodyParam] ARecord: TMyRecord): string; begin ARecord.Validate; // exception if fails --> 500 Internal server error Result := ARecord.ToString; end; Pros: - you have a single point of validation, automatically called during de-serialization - you may eventually follow a symmetrical approach for serialization, handling TMyRecord.ToJSONFilter(const AField: TRttiField; const AObj: TJSONObject): Boolean; - you can fine tune what to send to the client if validation fails (let the exception flow to MARS and have a 500 Internal server error or handle it and provide some different message / result / default) Cons: - validation is not compile checked (using field names as strings) and this may lead to maintainability issues on the long term; - even though you can take advantage of some AOP technique (i.e attributes on record fields) to generalize some validation rules, you'll need to replicate implementation of ToRecordFilter (and ValidationErrors-like fields) across all records of your data model (50 record types, 50 implementations, probably with some copy-and-paste trouble); *** Second approach (a variation of the first one) *** We can try to solve/mitigate the Cons of the previous approach by taking advantage of _AssignedValues field (automatically managed by the de-serialization mechanism) and adding a method collecting all validation rules: function Validate(out AMessages: TArray<string>): Boolean; overload; function TMyRecord.Validate(out AMessages: TArray<string>): Boolean; begin Result := True; AMessages := []; if Age < 18 then begin Result := False; AMessages := AMessages + ['Age: You must be 18+ to enter']; end; if IndexStr('DateOfBirth', _AssignedValues) = -1 then begin Result := False; AMessages := AMessages + ['DateOfBirth: Field is required']; end; if Result and (YearsBetween(DateOfBirth, Now) <> Age) then begin Result := False; AMessages := AMessages + ['DateOfBirth and Age mismatch']; end; end; procedure TMyRecord.Validate; var LMessages: TArray<string>; begin LMessages := []; if not Validate(LMessages) then raise Exception.Create(string.join(sLineBreak, LMessages)); end; So now, when our REST method calls Validate, validation actually occurs (so it's a bit deferred with respect to the previous approach) and validation is written without using strings to refer to fields (better compile time checking and less maintainability issues). Cons: - JSON raw data is hard to access at validation point. We may try to mix the two approaches or cache the raw JSON string in the record itself (if there are no memory usage concerns). *** Third approach: validation through readers *** A completely different approach would be to define a custom reader (inheriting from TRecordReader, MARS.Core.MessageBodyReaders.pas and overriding the ReadFrom method). This way you have a single point where you can access: - raw JSON data - de-serialized record (every kind of record you register the reader for) - IMARSActivation instance (basically everything about that invocation, including references to the current resource, method and so on) If you have several record type to deal with, I would probably go for this approach as you may then define your own custom attributes to define validation rules on your record definitions and enforce them in the ReadFrom method implementation (you have a TValue containing the record, you can inspect that record through Rtti to enumerate fields and their attributes to perform validation). You can then determine what to do with respect to the client through a corresponding MessageBodyWriter or through one of the error-handling capabilities MARS has (TMARSActivation.RegisterInvokeError or adding a method to your resources with the InvokeError attribute). The topic is a bit wide to cover every possible scenario. If you can, please specify a bit what you would like to implement / how many data structures you'll need to handle / what kind of approach with respect to the client you'd like to implement (sometime I've seen people willing to return a opaque 500 Internal server error, sometimes willing to return some details about what caused the error, some other wants full control over the response to provide a detailed error message, eventually in a different content type of the original one and so on...). I will try to help further (sorry for the late I am a bit overwhelmed this weeks). Sincerely,
  12. Impressive @RDPasqua! I can see a 8x speed up running a MARS "Hello World" project with 10K requests and a concurrency level of 100 (through Apache benchmark on localhost). It passes from 59.842 seconds to 7.617 (shrinking down total execution time to 12,7%). Of course my benchmark scenario can be improved (a Win10 VMWare Fusion machine, dual core over my MacBookPro late 2017, i7@3.5GHz). I made another test, querying a local Firebird (32bit) instance and performing a 'select * from EMPLOYEE' query with FireDAC to a (non-FireDAC) JSON serialization (about 10K bytes) and it passes from 33.640 seconds to 29.951 (shrinking down total execution time to 89%). So this time the gain is limited but still a good 10%. I am actually a bit puzzled how it's possible the first benchmark (textual 'Hello, World!') to be 200% slower than the second (returning a 42 records dataset JSON representation) but that has nothing to do with your memory manager replacement (although using RDPMM64 this does not happen anymore). I am attaching ab results for both scenarios. And on my MARS's to-do list now there's an entry to integrate Delphi-Cross-Socket as http server. 🙂 Congrats and keep up the good work! Andrea Benchmark_HelloWorld_DB_JSON_Response_Win64.txt Benchmark_HelloWorld_Text_Response_Win64.txt
  13. Andrea Magni

    Generating one-to-many on the fly with Live Bindings

    Another (small) addition (on the same path): 1) change SanitizeStr to this: function TForm1.SanitizeStr(const AString: string; const ADelimiter: string): string; begin Result := AString .Replace(' ' + ADelimiter, ADelimiter, [rfReplaceAll]) .Replace(ADelimiter + ' ', ADelimiter, [rfReplaceAll]); end; 2) change the SourceExpression to 'Owner.SanitizeStr(Text, Owner.ListBox1.Items.Delimiter)' And you can avoid setting the Items.Delimiter for your listbox. "It's a small step for man but..." :-)
  14. Andrea Magni

    Generating one-to-many on the fly with Live Bindings

    Not that I think that this would be a significant improvement with respect to your approach, but you can avoid the OnAssigningValue event-handler by introducing this public function to your form: function TForm1.SanitizeStr(const AString: string): string; begin Result := AString.Replace(' ,', ',', [rfReplaceAll]).Replace(', ', ',', [rfReplaceAll]); end; Then use as SourceExpression of your TBindExpression this value: 'Owner.SanitizeStr(Text)'. AFAIK "Owner" here stands for the owner of the BindingList and the LB expression engine is capable of calling a method on the underlying object (your form instance). Same lines of pascal code but one line left out from the XFM file (the one defining the event handler for OnAssigningValue event) and you can declare your function at your will (no prototype to match for the event handler). Does this match your definition of Better = writing less code? :-) Sincerely
  15. Basic tutorial on how to enable app shortcuts for your FMX apps. Delphi 10.3 Rio required (as the app shortcuts are available on Android since version 7.1, API Level 25). https://blog.andreamagni.eu/2019/02/how-to-add-android-app-shortcuts-to-a-fmx-application/ Sincerely, Andrea
×