Unsupported Media Type Spring ReST resource - angularjs

I have a simple method defined in my Rest Resource as below:
#RequestMapping(value = "/{studyId}/cases/{caseId}/exportlocation/{exportLocation}", method = RequestMethod.PUT)
#Timed
public void exportCase(#PathVariable Long studyId, #PathVariable Long caseId, #PathVariable String exportLocation,
#RequestBody Case acase) throws Exception {
log.debug("REST request to export Case {} for Study : {}", acase, studyId);
String exportFileName = exportService.exportCase(acase, "test");
// if (exportFileName == null) {
// response.sendError(HttpServletResponse.SC_NOT_FOUND, "Can't Export");
// }
// return exportFileName;
}
When I make a call on the page, I can see the URL as being /app/rest/studies/1/cases/1/exportlocation/test
I have the Request Mapping defined as
#RequestMapping(value = StudyResource.REQUEST_MAPPING, produces = MediaType.APPLICATION_JSON_VALUE)
#Secured(AuthoritiesConstants.USER)
public class StudyResource {
private final Logger log = LoggerFactory.getLogger(StudyResource.class);
public static final String REQUEST_MAPPING = "/app/rest/studies";
But keep getting a 415 Unsupported Media type. Can someone please look at the lines of code and tell me what is wrong. I highly appreciate your time and help.
My JS layer from where the calls are made on the page are as shown"
$scope.exportCase = function(studyId, caseId, exportLocation){
StudyService.updatecase.get({studyId:studyId,caseId:caseId}).$promise.then(function(acase){
$scope.acase = acase;
console.log(acase);
});
StudyService.exportcase.exportc({studyId: studyId,caseId:caseId,exportLocation:exportLocation},$scope.acase,
function () {
AND JS Service part below
exportcase : $resource('app/rest/studies/:studyId/cases/:caseId/exportlocation/:exportLocation', {}, {
'exportc' : {
method : 'PUT',
params : {
studyId : '#studyId',
caseId : '#caseId',
exportLocation : '#exportLocation'
}
},
})

Related

"Required request part 'image' is not present in" error in springboot with reactjs

When i try to upload a image in react, and send it to a spring boot api to save the file in a database, I get the following errors in spring boot:
2022-12-04 03:25:28.610 WARN 15080 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'image' is not present]
2022-12-04 03:25:28.631 WARN 15080 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.LinkedHashMap<java.lang.String,java.util.List<java.lang.String>>` from Array value (token `JsonToken.START_ARRAY`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.util.LinkedHashMap<java.lang.String,java.util.List<java.lang.String>>` from Array value (token `JsonToken.START_ARRAY`)<EOL> at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 1]]
Idk how to solve this. I've tried searching around, but havent found any answer. Im also unsure if the problem is in my react code or in my springboot code.
I think my error is in reactjs, because ive seen in other posts for people have somewhat the same problem, that their problem has been their react code. But I havent figured out what exactly might be wrong with my react code. Im also posting my spring boot code in case you may want to look at it,
My react code:
const FileUpload = () => {
const link = "http://localhost:8080/uploadimage?key"
var formData = new FormData()
const [fileName, setFilename] = useState("")
const [tags, setTags] = useState([])
{/* THIS IS CODE FOR SELECTING A FILE TO UPLOAD*/}
{/* IM TRYING TO DEFINE IMAGE BY PUTTING IT IN QUOTES IN FROMDATA.APPEND */}
const handleFile = (e) => {
setFilename(e.target.files[0].name)
console.log("handle file")
console.log(e.target.files[0])
formData.append("image", e.target.files[0])
}
const uploadFile = (e) => {
{/* THIS IS CODE FOR FILE UPLOADING*/}
console.log("sending...")
console.log(formData)
axios.post(
link,
formData, {
header: {
'Content-Type': 'multipart/form-data'
}
})
.then(res => {
console.log(res.data)
})
.catch(error => {
console.log(error)
})
{/* THIS IS CODE FOR SOMETHING ELSE; SOME TAGHANDLING */}
setTimeout(3000)
const taglink = "http://localhost:8080/givetags/" + fileName;
axios.post(taglink, tags)
.then(res => (
console.log(res.data)
))
}
{/* THIS IS CODE IS ALSO FOR SOMETHING ELSE*/}
function updateTags(e) {
const log = {...tags}
log[e.target.id] = e.target.value.split(" ")
setTags(log)
}
return (
<div>
<Container>
<Card.Title className='text-center mb-3'>Upload File</Card.Title>
<Form.Group controlId='file' className='mb-3'>
<Form.Control type='file' onChange={(e) => handleFile(e)}></Form.Control>
</Form.Group>
<Form.Group controlId='tags' className='mb-3'>
<Form.Control onChange={(e) => updateTags(e)} type="text" placeholder='Write tags'></Form.Control>
</Form.Group>
<Button onClick={(e) => uploadFile(e)}>Upload File</Button>
</Container>
<TagsConvention></TagsConvention>
</div>
)
}
export default FileUpload
This is my springboot code:
Controller:
#RestController
public class FileController {
#Autowired
private ImageServiceImpl service;
//==========================For uploading a file======================================
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping("/uploadimage")
public ResponseEntity<?> uploadImage(#RequestParam("image") MultipartFile file) throws IOException {
String uploadImage = service.uploadImage(file);
return ResponseEntity.status(HttpStatus.OK)
.body(uploadImage);
}
#CrossOrigin(origins = "http://localhost:3000")
#GetMapping("/getimage/{fileName}")
public ResponseEntity<?> downloadImage(#PathVariable String fileName){
byte[] file=service.downloadImage(fileName);
System.out.println(file);
return ResponseEntity.status(HttpStatus.OK)
.contentType(MediaType.valueOf(service.getType(fileName)))
.body(file);
}
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping("/givetags/{fileName}")
public ImageData giveImagetags(#PathVariable String fileName, #RequestBody Map<String, List<String>> tags) {
//return service.giveImageTags(fileName, tags);
//return service.giveImageTags(fileName, tags);
System.out.println(tags);
List<String> tagList = tags.get("tags");
return service.giveImageTags(fileName, tagList);
}
#CrossOrigin(origins = "http://localhost:3000")
#GetMapping("/getallimages")
public List<String> getAllImages() {
return service.getAllImages();
}
}
My model for the image:
#Entity
#Table(name = "ImageData")
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class ImageData implements Image{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long imageDataId;
private String name;
private String type;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name="file_has_tag",
joinColumns = {#JoinColumn(name="image_data_id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id")})
#JsonIgnoreProperties("image_data")
private Set<Tag> tags;
#Lob
#Column(name = "image_data",length = 1000)
private byte[] data;
#Override
public String toString() {
return "ImageData{" +
"imageDataId=" + imageDataId +
", name='" + name + '\'' +
", type='" + type + '\'' +
", tags=" + tags +
'}';
}
}
Service function for uploading a file:
public String uploadImage(MultipartFile file) throws IOException {
ImageData imageData = imageDataRepository.save(ImageData.builder()
.name(file.getOriginalFilename())
.type(file.getContentType())
.data(ImageUtils.compressImage(file.getBytes())).build()); //"data" is from the model class
System.out.println(imageData.toString());
if (imageData != null) {
return "file uploaded successfully : " + file.getOriginalFilename();
}
return null;
}
functions in utils class for compressing image
public static byte[] compressImage(byte[] data) {
Deflater deflater = new Deflater();
deflater.setLevel(Deflater.BEST_COMPRESSION);
deflater.setInput(data);
deflater.finish();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
byte[] tmp = new byte[4*1024];
while (!deflater.finished()) {
int size = deflater.deflate(tmp);
outputStream.write(tmp, 0, size);
}
try {
outputStream.close();
} catch (Exception ignored) {
}
return outputStream.toByteArray();
}
Ive tried to change the value of formdata, and the key in "image" in fromdata.append, but I havent figured it out. Ive also tried to search up the problem, but people have had different syntax problems from me, so idk what might be the problem.
There are two questions:
(1)Required request part 'image' is not present, the problem should be the parameter name problem, The #RequestParam("image") prefix must be "image". You can check if some parameter names are image.
(2)JSON parse error: In my opinion, the main cause of the problem is the API: givetags, #RequestBody Map<String, List> tags, which is not translated properly when receiving the react parameter. You can try using List to receive arguments.

Asp net core MVC Authorization with Active Directory

In an Asp net core MVC application, I use Active Directory for automatic login like this :
this.user = UserPrincipal.FindByIdentity(this.context, Environment.UserName);
and I get groups of the user with this :
public List<String> GetUserGroups()
{
List<String> groups = new List<String>();
foreach(GroupPrincipal gr in user.GetGroups())
{
groups.Add(gr.Name);
}
return groups;
}
And I would like to implement Autorisation with this groups, something like that :
[Authorize(Roles ="Admin")]
public IActionResult OnlyAdmin(){}
with something that link AD groups with authorization Roles or directly check authorization with AD groups if possible but I don't know how to do something like that.
note : I haven't any login/logout pages, it's only automatic.
EDIT
Don't know exactly why or how but it finaly work whithout any code and only with the user login in the PC not the user specified in this.user but it's fine like that.
But now I get a 404 error when I'm trying to access a denied page, why it's not a 401 or 403 error ? How can I redirect a denied access to a custom error page ?
You need to add the group in the ClaimsPrincipal class, i.e.
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, username));
foreach (string userGroup in authResponse)
{
claims.Add(new Claim(ClaimTypes.Role, userGroup, ClaimValueTypes.String,"system","system"));
}
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "authenticationScheme"));
Now use authorize attribute, either on controller or action as :
[Authorize(Roles = "guest,home")]
You can write an ErrorHandlingMiddleware as follows. You will need to register it in the startup file
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
following is an example for the same.
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> createLogger)
{
this._next = next;
this._logger = createLogger;
}
public async Task Invoke(HttpContext context)
{
var statusCode = HttpStatusCode.OK;
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
this._logger.LogError(ex, ex.Message);
switch (context.Response.StatusCode)
{
case (int)HttpStatusCode.NotFound:
statusCode = HttpStatusCode.NotFound;
break;
case (int)HttpStatusCode.Forbidden:
statusCode = HttpStatusCode.Forbidden;
break;
case (int)HttpStatusCode.BadRequest:
statusCode = HttpStatusCode.BadRequest;
break;
default:
statusCode = HttpStatusCode.InternalServerError;
break;
}
context.Response.StatusCode = (int)statusCode;
}
if (!context.Response.HasStarted)
{
context.Response.ContentType = "application/json";
var response = new { code = statusCode };
var json = JsonConvert.SerializeObject(response);
await context.Response.WriteAsync(json);
}
}
}

AngularJS/ Spring Boot application - automatically navigate to signin page after an idle time

I use AngularJS in frontend and Spring Boot/Spring Security in backend.
The backend looks like this:
#Component
public class TokenUtils {
public static final String MAGIC_KEY = "obfuscate";
public String createToken(final UserDetails userDetails) {
final long expires = System.currentTimeMillis() + 1000L * 60 * 60;
return userDetails.getUsername() + ":" + expires + ":" + computeSignature(userDetails, expires);
}
public String computeSignature(UserDetails userDetails, long expires) {
final StringBuilder signatureBuilder = new StringBuilder();
signatureBuilder.append(userDetails.getUsername()).append(":");
signatureBuilder.append(expires).append(":");
signatureBuilder.append(userDetails.getPassword()).append(":");
signatureBuilder.append(TokenUtils.MAGIC_KEY);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("No MD5 algorithm available!");
}
return new String(Hex.encode(digest.digest(signatureBuilder.toString().getBytes())));
}
public String getUserNameFromToken(final String authToken) {
if (null == authToken) {
return null;
}
final String[] parts = authToken.split(":");
return parts[0];
}
public boolean validateToken(final String authToken, final UserDetails userDetails) {
final String[] parts = authToken.split(":");
final long expires = Long.parseLong(parts[1]);
final String signature = parts[2];
final String signatureToMatch = computeSignature(userDetails, expires);
return expires >= System.currentTimeMillis() && signature.equals(signatureToMatch);
}
}
If user does not use the frontend for awhile and returns to frontend by e.g. a bottonclick, he is moved to the signin page.
My intention is that the application moves to signin page automatically after expiration time.
Is there a possibility to do this with AngularJS in frontend?
you may wanna use ng-idle plugin
how to configure ?
myApp.config(['KeepaliveProvider', 'IdleProvider', function(KeepaliveProvider, IdleProvider) {
IdleProvider.idle(5);
IdleProvider.timeout(5);
KeepaliveProvider.interval(10);
}]);
how to implement and listen timeouts in any scope ?
it is emitting some events at $rootScope. you can listen to it at any scope with
$scope.$on('IdleStart',fn) like functions
so you can call logout api endpoints & change route to login page

500 Error - Unable to select and perform a post action

I am not good with Web API. Here is my problem. I send an Json serialized object from my Windows Form Application. The object is an Entity table. When I do a get response it returns a 500 server error. Basically I plan to have multiple post methods in one controller which I may not be doing right. So I need you guys to guide me on what I have been doing wrong.
Here is my Controller:
[ResponseType(typeof(HttpWebResponse)), HttpPost, ActionName("MerchandiseApi")]
public HttpResponseMessage PostMain(IList<IMF_Main> mainFromConsolidator)
{
if (!ModelState.IsValid)
return Request.CreateResponse(HttpStatusCode.BadRequest, 2);
using (var anthill = new AnthillConsolidatorEntities())
{
var main = new IMF_Main();
foreach (var item in mainFromConsolidator)
{
main.BrandID = item.BrandID;
main.ItemID = item.ItemID;
main.CategoryID = item.CategoryID;
main.SubCategoryID = item.SubCategoryID;
main.ClassID = item.ClassID;
main.GenderID = item.GenderID;
main.CoaID = item.CoaID;
main.SubCoaID = item.SubCoaID;
main.First_SRP = item.First_SRP;
main.Current_SRP = item.Current_SRP;
main.Previous_SRP = item.Previous_SRP;
main.isSenior = item.isSenior;
main.isActive = item.isActive;
main.DateCreated = item.DateCreated;
anthill.IMF_Main.Add(main);
anthill.SaveChanges();
}
}
return Request.CreateResponse(HttpStatusCode.OK, 1);
}
Here's my WebApiConfig:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "MerchandiseApi",
routeTemplate: "api/{controller}/{action}"
);
}
Here is where the Uri gets built: I have 2 more tables to send but I will start with this. This goes to my first Post method to the server
var jsonMain = JsonConvert.SerializeObject(consolidatorEntities.IMF_Main, Formatting.None);
HttpPost("http://localhost:50826/api/Merchandise/PostMain", jsonMain) == 1.ToString()
public string HttpPost(string uri, string json)
{
string content = "";
try
{
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "POST";
request.Accept = "application/json";
request.ContentType = "application/json";
byte[] bodyBytes = Encoding.UTF8.GetBytes(json);
request.GetRequestStream().Write(bodyBytes, 0, bodyBytes.Length);
request.GetRequestStream().Close();
var response = (HttpWebResponse)request.GetResponse();
var sr = new StreamReader(response.GetResponseStream(), Encoding.GetEncod
ing("UTF-8"));
content = sr.ReadToEnd();
sr.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error sending data to Anthill \nException: " + ex, "Monytron - Consolidator", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return content;
}
Problem
The main problem is with your routing. Routes will check in order so when you post a request to http://localhost:50826/api/Merchandise/PostMain and you have these routes in order:
"api/{controller}/{id}"
"api/{controller}/{action}"
So the first route will match:
If your PostMain method is the only action with [HttpPost], then mainFromConsolidator will be null in your foreach loop you will receive a NullReferenceException that result in a 500 error.
If you have multiple method decorated with [HttpPost], then the call is ambiguous between those actions and you will receive an InvalidOperationExpception with "Multiple actions were found that match the request" message that result in a 500 error.
The other problem is you are using an ActionName("MerchandiseApi") but didn't post to that action.
Solution
You can use multiple solutions. As an option you can define only one route:
"api/{controller}/{action}/{id}"
This way you can create a controller that contains actions like these:
public class SomeController
{
// matches GET /api/some/action1
[HttpGet]
public HttpResponseMessage Action1()
// matches GET /api/some/action2/5
[HttpGet]
public HttpResponseMessage Action2(int id)
// matches POST /api/some/action3
[HttpPost]
public HttpResponseMessage Action3(SomeType someParameter)
// matches POST /api/some/action4
[HttpPost]
public HttpResponseMessage Action4(SomeType someParameter)
}
Anyway if you decide to define multiple routes, pay attention that routes will match in order and also if you used ActionName attribute, then use that name in url to call that action.

Self-hosted Nancy instance returning 404 errors

I'm trying to get a self-hosted Nancy app running, but I'm having trouble getting it to return valid responses. I'm new at Nancy; I expect my problem is something fairly simple.
Here's some code:
class Program
{
static void Main(string[] args)
{
const String PORT_SETTING = "webServicePortNumber";
const String URI = "http://localhost:{0}/download/";
var portNum = ConfigurationManager.AppSettings[PORT_SETTING];
var uri = new Uri(String.Format(URI, portNum));
var config = new HostConfiguration {
UrlReservations = new UrlReservations { CreateAutomatically = true }
};
using (var nancyHost = new NancyHost(new Bootstrapper(), config, uri)) {
nancyHost.Start();
Console.WriteLine(String.Format("Listening on {0}. Press any key to stop.", uri.AbsoluteUri));
Console.ReadKey();
}
Console.WriteLine("Stopped. Press any key to exit.");
Console.ReadKey();
}
}
internal class Bootstrapper : DefaultNancyBootstrapper
{
protected override Nancy.Diagnostics.DiagnosticsConfiguration DiagnosticsConfiguration
{
get {
return new DiagnosticsConfiguration {
Password = #"[password]"
};
}
}
}
My NancyModule looks like this:
public class DownloadsModule : NancyModule
{
public DownloadsModule() : base("/download")
{
RegisterRoutes();
}
private void RegisterRoutes()
{
Put["/"] = parms => InitiateDownload(parms);
Get["/"] = parms => Summary(parms);
Get["/{id}"] = parms => GetStatus(parms.requestId);
}
private Response GetStatus(Guid requestId)
{
return Response.AsText("TEST: GetStatus requestId " + requestId);
}
private Response Summary(dynamic parms)
{
return Response.AsText("Summary: You loved me before, do you love me now?");
}
private Response InitiateDownload(dynamic parms)
{
return Response.AsText("InitiateDownload.");
}
}
Nancy is running; I can access the diagnostics at http://127.0.0.1:8880/download/_Nancy/. Looking at them, the routes appear ready. Interactive Diagnostics/GetAllRoutes shows:
P U T
name: [nothing] path: /download
G E T
name: [nothing] path: /download
name: [nothing] path: /download/{id}
And yet, I'm getting 404s back when I try http://localhost:8880/download/.
The request trace on the diagnostics page shows:
Method: GET
Request Url:
Scheme: http
Host Name: localhost
Port: 8880
Base Path: /download
Path: /
Query:
Site Base: http://localhost:8880
Is Secure: false
Request Content Type:
Response Content Type: text/html
Request Headers:
<snip>
Accept: text/html;q=1
application/xhtml+xml;q=1
image/webp;q=1
application/xml;q=0.9
*/*;q=0.8
<snip>
Response Headers:
Status Code: 404
Log: New Request Started
[DefaultResponseNegotiator] Processing as real response
So why isn't Nancy routing this request to the proper route?
Problem pointed out to me by jchannon in the Nancy JabbR room:
The URI specifies http://localhost:{0}/download/, while the module also specifies a base path of /download, so currently its looking for an URL of http://localhost:{0}/download/download/

Resources