I have the following code which, I'd like to write in a way that is easy to manage and easy to understand.
RestBuilder rest = new RestBuilder()
if (someoption()) {
rest = rest.post(someurl) {
contentType: application/json
json {
payload1: somepayload
payload2: somepayload1
}
}
}
else {
rest = rest.post(someurl) {
contentType: application/json
json {
payload4: somepayload4
}
}
}
The only thing different in the if/else is the json payload. The above just shows one if/else, however, in my actual code I have multiple.
Is there an easy way to manage this? I tried puttind conditional statements in JSON closure but it didn't work
Here's a generalized rest call method that you could use:
RestResponse rest(String method, String url, Closure jsonData = null) {
RestBuilder rest = new RestBuilder()
rest."$method"(url) {
contentType: "application/json"
if (jsonData) {
json {
jsonData.delegate = delegate
jsonData()
}
}
}
}
and you could call it with
RestResponse response
if (someoption()) {
response = rest('post', someurl) {
payload1: somepayload
payload2: somepayload1
}
}
else {
response = rest('post', someurl) {
payload4: somepayload4
}
}
or
def json
if (someoption()) {
json = {
payload1: somepayload
payload2: somepayload1
}
}
else {
json = {
payload4: somepayload4
}
}
RestResponse response = rest('post', someurl, json)
Related
We have an endpoint that masks card numbers. I want to mock this endpoint with Wiremock. Since it will work for more than one card number, it is not possible for me to prepare a separate mock file for each card. How can I mock this endpoint using a single file? The request sent and the response returned to this request are as follows:
Request:
{
"cardNumber": "1234561234561234"
}
Response:
{
"maskedCard": "123456******1234"
}
I prepared a Wiremock file that works only for 1 card number:
{
"request": {
"method": "POST",
"url": "/maskedCard",
"bodyPatterns": [
{
"matchesJsonPath": "[?(#.cardNumber == '1234561234561234')]"
}
]
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"maskedCard": "123456******1234"
}
}
}
How can I make this work for all incoming card numbers?
If the cardNumber attached to the request does not matter, then I would just exclude the bodyPatterns for matching.
{
"request": {
"method": "POST",
"url": "/maskedCard"
}
If you then need to take whatever the cardNumber in the request body is and mask it, I'm not aware of any out of the box solution to do that for you. Instead you'll have to write a Response Transformer to change the unmasked cardNumber to the masked CardNumber. Something like...
public class MaskCardNumbers extends ResponseTransformer {
public String getName() {
return "MaskCardNumbers";
}
#Override
public Response transform(Request request, Response response, FileSource files, Parameters parameters) {
Pattern pattern = Pattern.compile("/maskedCard");
Matcher matcher = pattern.matcher(request.getUrl());
if(matcher.matches()) {
// Logic to extract cardNumber - my library of choice is JsonSimple
JSONObject responseBody = (JSONObject) parser.parse(response.getBodyAsString());
String cardNumber = responseBody.get("cardNumber").toString();
// Logic to replace cardNumber
String maskedCard = "";
for (int i = 0; i < cardNumber.length(); i++) {
if (i < 6 || i > 11) {
maskedCard += cardNumber.charAt(i);
} else {
maskedCard += "*";
}
}
// Create response JSON Object
JSONObject responseObject = new JSONObject();
responseObject.put("cardNumber", maskedCard)
// Return responseObject
return Response.Builder.like(response).but().body(responseBody.toJSONString()).build();
}
}
}
You'd then need to make sure that WireMock launches with the transformer attached. Finally, your mapping would look like:
{
"request": {
"method": "POST",
"url": "/maskedCard"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"maskedCard": "123456******1234"
}
}
}
You might need to play around with some specifics, but I think that should get the job done, or at least get you most of the way there.
I have my REST API where I put my pdf file, now I want my angular app to download it on click via my web browser but I got HttpErrorResponse
"Unexpected token % in JSON at position 0"
"SyntaxError: Unexpected token % in JSON at position 0↵ at JSON.parse (
this is my endpoint
#GetMapping("/help/pdf2")
public ResponseEntity<InputStreamResource> getPdf2(){
Resource resource = new ClassPathResource("/pdf-sample.pdf");
long r = 0;
InputStream is=null;
try {
is = resource.getInputStream();
r = resource.contentLength();
} catch (IOException e) {
e.printStackTrace();
}
return ResponseEntity.ok().contentLength(r)
.contentType(MediaType.parseMediaType("application/pdf"))
.body(new InputStreamResource(is));
}
this is my service
getPdf() {
this.authKey = localStorage.getItem('jwt_token');
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/pdf',
'Authorization' : this.authKey,
responseType : 'blob',
Accept : 'application/pdf',
observe : 'response'
})
};
return this.http
.get("http://localhost:9989/api/download/help/pdf2", httpOptions);
}
and invocation
this.downloadService.getPdf()
.subscribe((resultBlob: Blob) => {
var downloadURL = URL.createObjectURL(resultBlob);
window.open(downloadURL);});
I resolved it as follows:
// header.component.ts
this.downloadService.getPdf().subscribe((data) => {
this.blob = new Blob([data], {type: 'application/pdf'});
var downloadURL = window.URL.createObjectURL(data);
var link = document.createElement('a');
link.href = downloadURL;
link.download = "help.pdf";
link.click();
});
//download.service.ts
getPdf() {
const httpOptions = {
responseType: 'blob' as 'json')
};
return this.http.get(`${this.BASE_URL}/help/pdf`, httpOptions);
}
I solved the issue in this way (please note that I have merged multiple solutions found on stack overflow, but I cannot find the references. Feel free to add them in the comments).
In My service I have:
public getPDF(): Observable<Blob> {
//const options = { responseType: 'blob' }; there is no use of this
let uri = '/my/uri';
// this.http refers to HttpClient. Note here that you cannot use the generic get<Blob> as it does not compile: instead you "choose" the appropriate API in this way.
return this.http.get(uri, { responseType: 'blob' });
}
In the component, I have (this is the part merged from multiple answers):
public showPDF(fileName: string): void {
this.myService.getPDF()
.subscribe(x => {
// It is necessary to create a new blob object with mime-type explicitly set
// otherwise only Chrome works like it should
var newBlob = new Blob([x], { type: "application/pdf" });
// IE doesn't allow using a blob object directly as link href
// instead it is necessary to use msSaveOrOpenBlob
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(newBlob, fileName);
return;
}
// For other browsers:
// Create a link pointing to the ObjectURL containing the blob.
const data = window.URL.createObjectURL(newBlob);
var link = document.createElement('a');
link.href = data;
link.download = fileName;
// this is necessary as link.click() does not work on the latest firefox
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
setTimeout(function () {
// For Firefox it is necessary to delay revoking the ObjectURL
window.URL.revokeObjectURL(data);
link.remove();
}, 100);
});
}
The code above works in IE, Edge, Chrome and Firefox. However, I don't really like it, as my component is pulluted with browser specific stuff which will surely change over time.
For Angular 12+, I came up with something like this:
this.ApiService
.getFileFromApi()
.pipe(take(1))
.subscribe((response) => {
const downloadLink = document.createElement('a');
downloadLink.href = URL.createObjectURL(new Blob([response.body], { type: response.body.type }));
const contentDisposition = response.headers.get('content-disposition');
const fileName = contentDisposition.split(';')[1].split('filename')[1].split('=')[1].trim();
downloadLink.download = fileName;
downloadLink.click();
});
The subscribe is on a simple get() with the Angular HttpClient.
// api-service.ts
getFileFromApi(url: string): Observable<HttpResponse<Blob>> {
return this.httpClient.get<Blob>(this.baseApiUrl + url, { observe: 'response', responseType: 'blob' as 'json'});
}
You can do it with angular directives:
#Directive({
selector: '[downloadInvoice]',
exportAs: 'downloadInvoice',
})
export class DownloadInvoiceDirective implements OnDestroy {
#Input() orderNumber: string;
private destroy$: Subject<void> = new Subject<void>();
_loading = false;
constructor(private ref: ElementRef, private api: Api) {}
#HostListener('click')
onClick(): void {
this._loading = true;
this.api.downloadInvoice(this.orderNumber)
.pipe(
takeUntil(this.destroy$),
map(response => new Blob([response], { type: 'application/pdf' })),
)
.subscribe((pdf: Blob) => {
this.ref.nativeElement.href = window.URL.createObjectURL(pdf);
this.ref.nativeElement.click();
});
}
// your loading custom class
#HostBinding('class.btn-loading') get loading() {
return this._loading;
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
In the template:
<a
downloadInvoice
[orderNumber]="order.number"
class="btn-show-invoice"
>
Show invoice
</a>
My answer is based on #Yennefer's, but I wanted to use the file name from the server since I didn't have it in my FE. I used the Content-Disposition header to transmit this, since that is what the browser uses for a direct download.
First, I needed access to the headers from the request (notice the get method options object):
public getFile(): Observable<HttpResponse<Blob>> {
let uri = '/my/uri';
return this.http.get(uri, { responseType: 'blob', observe: 'response' });
}
Next, I needed to extract the file name from the header.
public getFileName(res: HttpResponse<any>): string {
const disposition = res.headers.get('Content-Disposition');
if (!disposition) {
// either the disposition was not sent, or is not accessible
// (see CORS Access-Control-Expose-Headers)
return null;
}
const utf8FilenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:; |$)/;
const asciiFilenameRegex = /filename=(["'])(.*?[^\\])\1(?:; |$)/;
let fileName: string = null;
if (utf8FilenameRegex.test(disposition)) {
fileName = decodeURIComponent(utf8FilenameRegex.exec(disposition)[1]);
} else {
const matches = asciiFilenameRegex.exec(disposition);
if (matches != null && matches[2]) {
fileName = matches[2];
}
}
return fileName;
}
This method checks for both ascii and utf-8 encoded file names, prefering utf-8.
Once I have the file name, I can update the download property of the link object (in #Yennifer's answer, that's the lines link.download = 'FileName.ext' and window.navigator.msSaveOrOpenBlob(newBlob, 'FileName.ext');)
A couple of notes on this code:
Content-Disposition is not in the default CORS whitelist, so it may not be accessible from the response object based on the your server's configuration. If this is the case, in the response server, set the header Access-Control-Expose-Headers to include Content-Disposition.
Some browsers will further clean up file names. My version of chrome seems to replace : and " with underscores. I'm sure there are others but that's out of scope.
//Step: 1
//Base Service
this.getPDF() {
return this.http.get(environment.baseUrl + apiUrl, {
responseType: 'blob',
headers: new HttpHeaders({
'Access-Control-Allow-Origin': '*',
'Authorization': localStorage.getItem('AccessToken') || ''
})
});
}
//Step: 2
//downloadService
getReceipt() {
return new Promise((resolve, reject) => {
try {
// {
const apiName = 'js/getReceipt/type/10/id/2';
this.getPDF(apiName).subscribe((data) => {
if (data !== null && data !== undefined) {
resolve(data);
} else {
reject();
}
}, (error) => {
console.log('ERROR STATUS', error.status);
reject(error);
});
} catch (error) {
reject(error);
}
});
}
//Step 3:
//Component
getReceipt().subscribe((respect: any) => {
var downloadURL = window.URL.createObjectURL(data);
var link = document.createElement(‘a’);
link.href = downloadURL;
link.download = “sample.pdf";
link.click();
});
This also works in IE and Chrome, almost the same answer only for other browsers the answer is a bit shorter.
getPdf(url: string): void {
this.invoiceService.getPdf(url).subscribe(response => {
// It is necessary to create a new blob object with mime-type explicitly set
// otherwise only Chrome works like it should
const newBlob = new Blob([(response)], { type: 'application/pdf' });
// IE doesn't allow using a blob object directly as link href
// instead it is necessary to use msSaveOrOpenBlob
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(newBlob);
return;
}
// For other browsers:
// Create a link pointing to the ObjectURL containing the blob.
const downloadURL = URL.createObjectURL(newBlob);
window.open(downloadURL);
});
}
I've implemented a REST endpoint in ballerinalang called https://localhost:9090/isValidUser. And here is my code below
import ballerina.net.http;
#http:configuration {
basePath:"/",
httpsPort:9090,
keyStoreFile:"${ballerina.home}/bre/security/wso2carbon.jks",
keyStorePassword:"wso2carbon",
certPassword:"wso2carbon",
trustStoreFile:"${ballerina.home}/bre/security/client-truststore.jks",
trustStorePassword:"wso2carbon"
}
service<http> authentication {
#http:resourceConfig {
methods:["POST"],
path:"/isValidUser"
}
resource isValidUser (http:Request req, http:Response res) {
println(req.getHeaders());
res.send();
}
}
Now I need to do is when I invoke that URL from the browser, I need to redirect the user to another URL called https://localhost:3000 after some validations happen within my service.
So how can I do this redirection from ballerinalang?
Ballerina has provided smooth API to do the redirection. Please check following code which elaborates the Listener endpoint redirection.
service<http:Service> redirect1 bind {port:9090} {
#http:ResourceConfig {
methods:["GET"],
path:"/"
}
redirect1 (endpoint client, http:Request req) {
http:Response res = new;
_ = client -> redirect(res, http:REDIRECT_TEMPORARY_REDIRECT_307,
["http://localhost:9093/redirect2"]);
}
}
The complete example is available in
Ballerina Redirects
In Ballerina, you need to handle the redirects yourself by setting the necessary headers and status code. The following example is a simple demo of how you can redirect in Ballerina. (note: I tried this in Ballerina 0.95.2)
import ballerina.net.http;
#http:configuration {basePath:"/hello"}
service<http> helloWorld {
#http:resourceConfig {
methods:["GET"],
path:"/"
}
resource sayHello (http:Request request, http:Response response) {
map qParams = request.getQueryParams();
var name, _ = (string)qParams.name;
if (isExistingUser(name)) {
response.setStringPayload("Hello Ballerina!");
} else {
response.setHeader("Location", "http://localhost:9090/hello/newUser");
response.setStatusCode(302);
}
_ = response.send();
}
#http:resourceConfig {path:"/newUser"}
resource newUser (http:Request request, http:Response response) {
string msg = "New to Ballerina? Welcome!";
response.setStringPayload(msg);
_ = response.send();
}
}
function isExistingUser (string name) (boolean) {
return name == "Ballerina";
}
I'm working on a Angular 2 service and I'm trying to DRY up my code. My service returns data from an RESTful API (GET request). For the moment i only send one param (auth_token) but how can i deal with a call to my service with extra params ?
Here my basic call to my service:
this._dashboardService.getData('users')
.subscribe(usersData=> {
if (usersData.ok) {
this.users= usersData.json();
});
Here is my service :
getData(data) {
let authToken = localStorage.getItem('auth_token');
let params = new URLSearchParams();
params.set('auth_token', authToken);
return this._http
.get(this._url+data,{ search: params })
.map(data=> {
if (data.status===200) {
return data;
}
});
}
I want to make this code work :
this._dashboardService.getData('users',oneParameter)
.subscribe(usersData=> {
if (usersData.ok) {
this.users= usersData.json();
});
I'm not sure how to proceed to make this (is it even possible ?)
Any ideas ?
You can pass your new object param and assign to another object that contains the auth_token.
getData(data, anotherParams) {
let params = new URLSearchParams();
let authToken = localStorage.getItem('auth_token');
var myObject = Object.assign({'auth_token':authToken}, anotherParams);
for (var name in myObject) {
params.set(name, myObject[name]);
}
return this._http
.get(this._url+data,{ search: params })
.map(data=> {
if (data.status===200) {
return data;
}
});
}
I have 1 domain class, 1 controller, 1 URL mapping (see below). I want to send request, and receive JSON response.
But currently my request is correctly mapped to the correspondent controller method, the method is successfully executed, and then I get an error with message that jsp-file not available.
How to explain Grails, that I don't need no jsp-files: I want to receive/parse JSON requests, and send JSON responses right from my controllers?
class Brand {
String name
String description
String logoImageURL
static constraints = {
name(blank: false)
}
}
---------------------------
class UrlMappings {
static mappings = {
"/brands"(controller: "brand", parseRequest: true, action: "list")
}
}
---------------------------
import grails.converters.JSON
class BrandController {
def list = {
return Brand.list() as JSON
}
def show = {
return Brand.get(params.id) as JSON
}
def save = {
def brand = new Brand(name: params.name, description: params.description, logoImageURL: params.logoURL)
if (brand.save()) {
render brand as JSON
} else {
render brand.errors
}
}
}
============== Error message ===============
Request URL: http://localhost:8080/ShoesShop/brands
message /ShoesShop/WEB-INF/grails-app/views/brand/list.jsp
description The requested resource (/ShoesShop/WEB-INF/grails-app/views/brand/list.jsp) is not available.
It should work if you instead do:
def list = {
render Brand.list() as JSON
}