I have a spring endpoint that serves a pdf as a byte[] and a React ui that is getting a 406 when I try to call the endpoint.
spring endpoint:
#GetMapping(value = "report/{report_id}", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<InputStreamResource> generateReviewTaskReport(
HttpServletResponse response,
#PathVariable("report_id") String reportId,
#RequestAttribute(USER_ID) String loginId) {
byte[] report = reportService.generateReport(reportId, loginId);
ByteArrayInputStream inputStream = new ByteArrayInputStream(report);
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(report.length);
headers.add("Content-Disposition", "inline;filename=" + reportId + "_report.pdf");
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_PDF)
.body(new InputStreamResource(inputStream));
}
I've tried:
headers.add("Content-Disposition", "attachment;filename=" + reportId + "_report.pdf");
same result.
react request:
export const getReport = (reportId = '') => (dispatch) => {
const report = `${apiConfig.reportUrl}${reportId}`
const promise = axios.get(report,
{
responseType: 'blob',
headers: {
'Accept': 'application/pdf'
}
})
return dispatch({
type: GET_REPORT,
payload: promise,
})
}
case GET_REPORT:
if (payload.data) {
const report = new Blob([payload.data])
reportUrl = URL.createObjectURL(report)
window.open(reportUrl, "_blank")
}
I've tried responseType: 'bufferArray', returning a plain byte[] from my spring endpoint, always get a 406. I'm guessing it's because I have the wrong mime type in my 'Accept' header. I've tried 'application/pdf' and '*/*', same result. What headers do I need to accept an InputStreamResource or byte[]?
With postman I can download the file just fine.
my config:
#Component
public class WebConfiguration extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(byteArrayHttpMessageConverter());
converters.add(new ResourceHttpMessageConverter());
}
#Bean
public HttpMessageConverter byteArrayHttpMessageConverter() {
ByteArrayHttpMessageConverter arrayHttpMessageConverter =
new ByteArrayHttpMessageConverter();
arrayHttpMessageConverter.setSupportedMediaTypes(getSupportedMediaTypes());
return arrayHttpMessageConverter;
}
private List<MediaType> getSupportedMediaTypes() {
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_PDF);
mediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
return mediaTypes;
}
}
A general solution, but i think in you'r case it should works fine ;)
axios({
url: 'http://api.dev/file-download', //your url
method: 'GET',
responseType: 'blob', // important
}).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.pdf'); //or any other extension
document.body.appendChild(link);
link.click();
});
gist: https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743
Full credits to: https://gist.github.com/javilobo8
Related
I am developing a react app using Spring Boot backend.
I am not very friendly with Java or Spring Boot and now I am stuck with an issue.
Bellow is backend method code which is handling the request
#RestController
#RequestMapping("/toys")
public class ToysController {
#Autowired
private ToysService toysService;
#CrossOrigin(origins = "*")
#PostMapping("/addtoy")
public ResponseEntity<Toys> addToy(#RequestParam("file") MultipartFile file,
#RequestParam("toyName") String toyName,
#RequestParam("toyDescription") String toyDescription,
#RequestParam("toyPrice") Long toyPrice,
#RequestParam("quantity") int quantity,
#RequestParam("availability") Boolean availability,
#RequestParam("sellerId") Long sellerId) throws IOException{
Toys toy = new Toys();
toy.setToyName(toyName);
toy.setToyDescription(toyDescription);
toy.setQuantity(quantity);
toy.setToyPrice(toyPrice);
toy.setAvailability(availability);
toy.setSellerId(sellerId);
toy.setToyImage(ImageEncoderDecoder.compressBytes(file.getBytes()));
toy.setImageName(file.getOriginalFilename());
Toys toyRes = toysService.addToy(toy);
if(toyRes!=null) {
toyRes.setToyImage(ImageEncoderDecoder.decompressBytes(toyRes.getToyImage()));
return new ResponseEntity<Toys>(toyRes,HttpStatus.OK);
}
else {
return new ResponseEntity<Toys>(HttpStatus.NO_CONTENT);
}
}
}
Below are the request details I am supposed to use
API: http://localhost:8081/toys/addtoy
Method: POST
Body:
[{"key":"toyName","value":"Car","type":"text","enabled":true},
{"key":"toyDescription","value":"small car 6 month old","type":"text","enabled":true},
{"key":"toyPrice","value":"600","type":"text","enabled":true},
{"key":"quantity","value":"2","type":"text","enabled":true},
{"key":"availability","value":"true","type":"text","enabled":true},
{"key":"file","type":"file","enabled":true,"value":[]},
{"key":"sellerId","value":"1","type":"text","enabled":true}]
Response: Status code 200
Following is how I am trying to hit the API
export const addToy = toy => {
const requestOptions = {
mode: 'cors',
method: 'POST',
headers: {
"Content-Type": "multipart/form-data; boundary=%%",
'Access-Control-Allow-Origin' : '*'
},
body: toy
};
fetch(`${API}/toys/addtoy`, requestOptions) // API = 'http://localhost:8081'
.then(response => {
if(response.status === 200)
console.log('Toy Inserted');
console.log('Resopose : ', response);
})
.catch(err => {
console.log('Error while inserting toy : ', err);
})
}
Calling the above method
const handleSubmit = e => {
e.preventDefault()
let formData = new FormData()
formData.append('toyName', toyName)
formData.append('toyDescription', toyDesc)
formData.append('toyPrice', parseInt(toyPrice))
formData.append('quantity', parseInt(toyQty))
formData.append('availability', parseInt(toyQty) > 0)
formData.append('file', image)
formData.append('sellerId', parseInt(loggedIn.loggedInUser.userId))
addToy(formData)
}
The response I am getting back
body: ReadableStream
locked: false
bodyUsed: false
headers: Headers {}
ok: false
redirected: false
status: 400
statusText: ""
type: "cors"
url: "http://localhost:8081/toys/addtoy"
I have an application running on http://localhost:8181/ which has React integrated with Spring Boot. (both run on the same port).
I send this POST request to http://localhost:8181/public/authenticate using axios:
The axios instance:
export const axios_register_login_user = axios.create({
baseURL: '/public',
withCredentials: true,
headers: { "content-type": "application/json" },
method: 'POST'
})
The login request:
export async function login(username, password, callback) {
axios_register_login_user.post("/authenticate", {
'username': username,
'password': password
}).then(response => {
console.log("success", response);
callback(response.data);
}).catch(error => {
console.log("failed", error);
callback("Failed");
})
}
The login is successful and I can see a cookie being returned in the response
However, this cookie is not set in the Application->Cookies tab
Here's my API code:
#RequestMapping(value = "/authenticate", method = RequestMethod.POST, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<?> authenticateUser(#RequestBody AuthenticationRequest request, HttpServletResponse response) {
try {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword());
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
} catch (BadCredentialsException e) {
throw new BadCredentialsException("Invalid details");
}
UserDetailsImpl userDetails = (UserDetailsImpl) userDetailsService.loadUserByUsername(request.getUsername());
String name = userDetails.getUser().getName();
String generatedToken = jwtUtil.generateToken(userDetails);
Cookie cookie = new Cookie("jwt", generatedToken);
cookie.setMaxAge(60 * 60 * 10);//like JWT, the max age is 10 hours
// cookie.setSecure(false);
cookie.setHttpOnly(true);
response.addCookie(cookie);
return new ResponseEntity<>(new AuthenticationResponse(name + " " + generatedToken), HttpStatus.OK);
}
I tried adding
#CrossOrigin(allowCredentials = "true", origins = "{http://localhost:3000,http://localhost:8181}")
to the above method but it didn't help.
Please help me out with this. I've been stuck here for 2 days now :(
I was missing cookie.setPath("/") in my code.
Additionally, I also did cookie.setSecure(false);
After these changes, the cookie was added to the browser
I am trying to pass array of names to spring controller using axios get request.if i try to pass single value in params it works fine but if pass array in params then i am getting error "CORS header ‘Access-Control-Allow-Origin’ missing". I tried this
this is url
http://localhost:8080/onlineshopping/view/category/products?name[]=Alex&name[]=john
taskAction.js
var request = {
params: {
name : JSON.parse(localStorage.getItem('name'))
}
}
const res = await axios.get(`http://localhost:8080/onlineshopping/view/category/products`,request);
dispatch({
type: GET_CATEGORY_PRODUCTS,
payload: res.data
});
};
but this is not working
My spring controller
#RequestMapping(value = "/view/category/products")
public Map<String, Object> viewProducts(
#RequestParam(value = "name[]", required = false) List<String> name,
HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> mapproducts = new HashMap<String, Object>();
for (String Str : name) {
System.out.println("name " + Str);
}
You can use querystring parsing and stringifying library 'qs'.
import Qs from 'qs'
params = {
name : JSON.parse(localStorage.getItem('name'))
}
let myAxios = axios.create({
paramsSerializer: params => Qs.stringify(params, {arrayFormat: 'repeat'})
})
const res = await
myAxios.get(`http://localhost:8080/onlineshopping/view/category/products`, {params});
dispatch({
type: GET_CATEGORY_PRODUCTS,
payload: res.data
});
};
you will get url like this
http://localhost:8080/onlineshopping/view/category/products?name=Alex&name=john
and in spring controller you can split string using
Arrays.asList(name.split("\\s*,\\s*"))
spring controller
#RequestMapping(value = "/view/category/products")
public Map<String, Object> viewProducts(
#RequestParam(value = "name", required = false) String name,
HttpServletRequest request, HttpServletResponse response) {
List<String> name = Arrays.asList(name.split("\\s*,\\s*"));
I am new to Identity Server. I haven't configured it before. But I need it for a Project I am working on.
The API will be serving an Angular JS Client, iOS App and an Android App. We need to implement authentication and authorisation and custmer grant
Note: I am trying to configure Identity Server and my API in the same Web API project.
I have followed the documentation and configured Identity Server as the following:
In startup.cs, in ConfigureServices()
private readonly IConfiguration config;
private const string DEFAULT_CORS_POLICY = "localhost";
public Startup (IConfiguration config) => this.config = config;
public void ConfigureServices (IServiceCollection services) {
services.AddIdentityServer ()
.AddDeveloperSigningCredential ()
//.AddInMemoryApiResources(config.GetSection("ApiResources"))
.AddInMemoryApiResources (Config.GetApis ())
//.AddInMemoryClients(config.GetSection("Clients"))
.AddInMemoryClients (Config.GetClients ())
.AddInMemoryIdentityResources (Config.GetIdentityResources ())
//.AddInMemoryIdentityResources(config.GetSection("IdentityResources"))
.AddExtensionGrantValidator<WechatGrantValidator> ();
services.AddTransient<IUserCodeValidator, UserCodeValidator> ();
services.AddCors (options => {
options.AddPolicy (DEFAULT_CORS_POLICY, builder => {
builder.WithOrigins ("http://localhost:5202");
builder.AllowAnyHeader ();
builder.AllowAnyMethod ();
});
});
}
I implemented the interface IExtensionGrantValidator and register the extension grant
public class WechatGrantValidator : IExtensionGrantValidator {
private IUserCodeValidator validator;
public WechatGrantValidator (IUserCodeValidator validator) {
this.validator = validator;
}
public string GrantType => "wechat_grant";
public async Task ValidateAsync (ExtensionGrantValidationContext context) {
string userCode = context.Request.Raw.Get ("userCode");
var result = await validator.ValidateAsync (userCode);
if (result.IsError) {
context.Result = new GrantValidationResult (TokenRequestErrors.InvalidGrant);
return;
}
context.Result = new GrantValidationResult (result.UserId, GrantType);
return;
}
}
I have followed the documentation and configured client infos as the following
public static IEnumerable<Client> GetClients () {
return new Client[] {
new Client {
ClientId = "javascritpClient",
ClientName = "JavaScript Client",
AllowedGrantTypes = { "wechat_grant" },
AllowAccessTokensViaBrowser = true,
AllowedCorsOrigins = { "http://localhost:5202" },
AllowedScopes = { "api1" },
ClientSecrets = { new Secret ("secret".Sha256 ()) }
}
};
}
Now because I want to use it Angular JS, iOS and Android I want to just get the Access Token from the IdentityServer, and then use the Access Token for Authentication and Authorisation.
for this I am trying to access the /connect/token from a JS client
But I am getting an invalid_client error.
#Injectable()
export class OauthService {
private http: Http;
public constructor(http: Http) {
this.http = http;
}
public async getDiscoveryInfos(issuer: string): Promise<DiscoveryInfos> {
if (!issuer.endsWith('/')) {
issuer += '/';
}
issuer += '.well-known/openid-configuration';
return this.http.get(issuer).map(response => {
return response.json();
}).toPromise();
}
public async getToken(): Promise<any> {
const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded" });
const discovery = await this.getDiscoveryInfos('http://localhost:5200');
return this.http.post(discovery.token_endpoint, {
grant_type: 'wechat_grant',
userCode: 'userCodeAA',
client_id: 'javascritpClient',
client_secret: 'secret',
scope:'api1'
}, { headers: headers }).map(response => response.json()).toPromise();
}
}
http response infos
The server response "error":"invalid_client"
log infos
The error I get on the server side is 'No client identifier found':
1 - Why am I getting this error?
2 - As I need to get the Token programmatically in JS, I need to use /connect/token, am I correct on this? Am I on the correct path?
in ng2 use a method like bellow:
public Token(data: SigninModel): Observable<any> {
this.options = new RequestOptions({ headers: this.headers });
this.headers.append('Content-Type', 'application/x-www-form-urlencoded');
const url = this.urlBase + `connect/token`;
const param = new URLSearchParams();
param.set('grant_type', 'password');
param.set('client_Id', 'javascritpClient');
param.set('client_secret', 'secret');
param.set('scope', 'offline_access');
param.set('username', data.username);
param.set('password', data.password);
return this.http.post(url, `${param.toString()}`, this.options)
.map((response: Response) => {
return (response.json());
})
.catch(this.handleError);
}
I am trying to access REST web service from angularjs. I am not able to call it successfully.
AngularJs Code
var singleOrderUrl = "/singleOrder/retrieve";
function getSingleOrderDetails(userName,singleOrderUrl,$http,$q) {
var fd = new FormData();
var deffered = $q.defer();
fd.append('USERNAME', 'test123');
//fd.append();
//fd.append();
console.log("inside service"+userName+"singleOrderUrl:::"+singleOrderUrl);
return $http.get(singleOrderUrl, fd, {
withCredentials : false,
transformRequest : angular.identity,
headers : {
'Content-Type' : undefined,
}
}).success(function(response) {
console.log(response);
responseData = response.data.toString();;
deffered.resolve(response);
return responseData;
}).error(function(error) {
alert("error");
deffered.reject(error);
return "failed";
});
};
Rest Service code
#RestController
public class SingleOrderHistoryController {
private static final Logger logger = LoggerFactory.getLogger(SingleOrderHistoryController.class.getName());
#RequestMapping(value = "/singleOrder/retrieve", method=RequestMethod.GET, produces="application/json")
public List<SingleHistoryRecord> getSingleOrderDetails(#RequestParam(value = Constants.USER_NAME, required = true) String userName, HttpServletRequest request,HttpServletResponse response) throws Exception {
logger.debug("inside SingleOrderHistoryController ");
List<SingleHistoryRecord> singleOrderHistoryList = new ArrayList<SingleHistoryRecord>();
SingleHistoryRecord record1 = new SingleHistoryRecord();
SingleHistoryRecord record2 = new SingleHistoryRecord();
record1.setClientIdentifier(userName);
record1.setSubmitDate("01/05/2017");
record1.setStatus("Complete");
record1.setReferenceID("1234555");
record1.setOrderID("test123");
record2.setClientIdentifier(userName);
record2.setSubmitDate("01/05/2017");
record2.setStatus("Complete");
record2.setReferenceID("1234555");
record2.setOrderID("test123");
singleOrderHistoryList.add(record1);
singleOrderHistoryList.add(record2);
return singleOrderHistoryList;
}
Can anyone please advise what I am doing wrong here, It is getting the source code of the page in response instead of getting the list.