Home > Mobile >  Current request is not a multipart request in Angular but works in Postman
Current request is not a multipart request in Angular but works in Postman

Time:10-19

I have a working upload backend with Postman. But I can't make it work from angular.

Backend code:

package com.riaadvisory.dih.web.api.file;

import static java.nio.file.Files.copy;
import static java.nio.file.Paths.get;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.esotericsoftware.minlog.Log;

@RestController
@RequestMapping(value = "/files")
public class FileUploadService {

    @PostMapping(value = "/upload", consumes = MediaType.ALL_VALUE)
    public ResponseEntity<List<String>> uploadFiles(@RequestParam("files") MultipartFile[] files) {

        Log.info("Processing file upload...");

        List<String> exceptions = new ArrayList<>();

        // Upload directory
        final String DIRECTORY = System.getProperty("user.home")   "/Documents/Uploads";

        List<String> fileNames = new ArrayList<>();
        for (MultipartFile file : files) {
            String fileName = file.getOriginalFilename();
            try {
                Path fileStorage = get(DIRECTORY, fileName).toAbsolutePath().normalize();
                copy(file.getInputStream(), fileStorage, REPLACE_EXISTING);
            } catch (Exception e) {
                exceptions.add(e.getMessage());
                e.printStackTrace();
            }
            fileNames.add(fileName);
        }

        if (!exceptions.isEmpty()) {
            return ResponseEntity.badRequest().body(exceptions);
        }

        return ResponseEntity.ok().body(fileNames);

    }

}

Here's a sample execution in Postman that returns the list of uploaded files: enter image description here

As seen in the image above, the backend code is working as expected.

upload.component.ts

import { Component } from '@angular/core';
import { AppConfigService } from 'src/shared/services/app-config.service';
import getClassNameForExtension from 'font-awesome-filetypes';
import { HttpClient } from '@angular/common/http';
import { NgxSpinnerService } from 'ngx-spinner';

@Component({
  selector: 'my-app',
  templateUrl: './upload.component.html',
  styleUrls: ['./upload.component.scss'],
})
export class UploadComponent {
  files = [];
  totalSize: number = 0;
  maxUploadSize: number;
  fileExtensions: Array<string> = [];
  hasInvalidFile: boolean = false;

  constructor(
    private appConfigService: AppConfigService,
    private spinner: NgxSpinnerService,
    private http: HttpClient
  ) {}

  ngOnInit() {
    this.maxUploadSize = this.appConfigService.configData.maxUploadSize;
    if (this.appConfigService.configData.fileExtensions) {
      const extensions =
        this.appConfigService.configData.fileExtensions.split(',');
      extensions.forEach((ext) => {
        this.fileExtensions.push(ext.trim());
      });
    }
  }

  onFileDropped($event) {
    this.prepareFilesList($event);
  }

  fileBrowseHandler(files) {
    this.prepareFilesList(files);
  }

  deleteFile(index: number) {
    let newSize = 0;
    this.files.splice(index, 1);

    let allValid = true;
    this.files.forEach((file) => {
      if (file.invalidFileExtension) {
        allValid = false;
      }
      newSize  = file.size;
    });

    this.hasInvalidFile = !allValid;
    this.totalSize = newSize;
  }

  uploadFilesSimulator(index: number) {
    setTimeout(() => {
      if (index === this.files.length) {
        return;
      } else {
        const progressInterval = setInterval(() => {
          if (this.files[index]) {
            if (this.files[index].progress === 100) {
              clearInterval(progressInterval);
              this.uploadFilesSimulator(index   1);
            } else {
              this.files[index].progress  = 5;
            }
          }
        }, 200);
      }
    }, 1000);
  }

  prepareFilesList(files: Array<any>) {
    for (const file of files) {
      // const ext = file.name.substr(file.name.lastIndexOf('.')   1);
      file.progress = 0;

      const extension = file.name.split('.').pop();
      file.extension = extension;

      const className = getClassNameForExtension(extension);
      file.className = className;

      if (
        this.fileExtensions.length > 0 &&
        !this.fileExtensions.includes(extension)
      ) {
        file.invalidFileExtension = true;
        this.hasInvalidFile = true;
      }

      this.files.push(file);
      this.totalSize  = file.size;
    }
    this.uploadFilesSimulator(0);
  }

  /**
   * Format size in bytes
   * @param bytes (File size in bytes)
   * @param decimals (Decimals point)
   */
  formatBytes(bytes) {
    if (bytes === 0) {
      return '0 Bytes';
    }
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2))   ' '   sizes[i];
  }

  upload() {
    const formData = new FormData();

    for (const file of this.files) {
      formData.append('files', file);
    }

    this.spinner.show();
    this.http.post(`http://localhost:16080/files/upload`, formData);
  }
}

upload.component.html

<div  style="height: 87vh">
  <!-- Spinner -->
  <ngx-spinner
    bdColor="rgba(189,188,188,0.6)"
    size="medium"
    color="#4a4848"
    type="ball-beat"
    [fullScreen]="true"
  >
  </ngx-spinner>
  <div
    
    style="padding: 1.25%; padding-bottom: 0; height: 99%"
  >
    <mat-card
      
      style="height: 100%; overflow: auto"
    >
      <!-- Card-header -->
      <div >
        <!-- Breadcrumbs -->
        <div >
          <i
            [matTooltip]="''"
            matTooltipClass="mat-tool-cust"
            
          ></i>
          <span style="padding: 0 8px"> Data Processing </span>
        </div>
      </div>
      <!-- Card Content -->
      <div  (fileDropped)="onFileDropped($event)">
        <form method="post" enctype="multipart/form-data">
          <input
            type="file"
            #fileDropRef
            id="fileDropRef"
            multiple
            (change)="fileBrowseHandler($event.target.files)"
          />
          <i ></i>
          <h3>Drag and drop files here</h3>
          <h3>or</h3>
          <label
            for="fileDropRef"
            
            >Click here to browse files</label
          >
        </form>
      </div>
      <div >
        <div  *ngFor="let file of files; let i = index">
          <div >
            <i  style="font-size: 20px"></i>
          </div>
          <div >
            <h4
              
              [ngClass]="file.invalidFileExtension ? 'strike-through' : ''"
            >
              {{ file?.name }}
            </h4>
            <p >
              {{ formatBytes(file?.size) }}
            </p>
            <app-progress [progress]="file?.progress"></app-progress>
          </div>

          <div  (click)="deleteFile(i)">
            <i  matTooltip="Delete"></i>
          </div>
        </div>
      </div>
      <div
        
        *ngIf="
          files.length > 0 && (hasInvalidFile || totalSize > maxUploadSize)
        "
      >
        <div style="justify-content: center; color: red">
          Files cannot be uploaded. Some files may not be in the supported
          format ({{ fileExtensions }}) or the total allowable upload size may
          have exceeded {{ formatBytes(maxUploadSize) }}.
        </div>
      </div>
      <div
        
        *ngIf="
          files.length > 0 && !hasInvalidFile && totalSize <= maxUploadSize
        "
      >
        <button
          type="submit"
          (click)="upload()"
          mat-raised-button
          
        >
          Upload Files
        </button>
      </div>
    </mat-card>
  </div>
</div>

Payload enter image description here

Error I'm getting

nested exception is org.springframework.web.multipart.MultipartException: Current request is not a multipart request] with root cause
org.springframework.web.multipart.MultipartException: Current request is not a multipart request

I can see a slight difference in the payload from Postman as it gives the full path. But that is not possible to achieve in javascript for security purpose. enter image description here

CodePudding user response:

Provide the header Content-Type: file, It should resolve the issue.

this.http.post(`http://localhost:16080/files/upload`, formData, 
      headers: {
        'Content-Type': 'file'
      }
);

Or

const _headers = new HttpHeaders({
   'Content-Type': 'file'
});

this.http.post(`http://localhost:16080/files/upload`, formData, 
      headers: _headers
);

CodePudding user response:

My original code is working as expected. The problem is in our project, there's an HttpInterceptor. that automatically appends a header of 'Content-Type', 'application/json' if it wasn't passed.

I added this in my http call.

const headers = new HttpHeaders().set('isUpload', 'true');
this.http.post(`http://localhost:16080/files/upload`, formData, {headers});

And in the interceptor, I added a condition to preserve the previous logic.

import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpParams,
} from '@angular/common/http';
import { MatDialogRef } from '@angular/material/dialog';
import { MessageComponent } from '../message/message.component';

@Injectable({
  providedIn: 'root',
})
export class RequestInterceptorService implements HttpInterceptor {
  constructor() {}

  etagValue = undefined;

  // error dialog ref var
  dialogRef!: MatDialogRef<MessageComponent>;

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    request = request.clone({
      withCredentials: true,
      headers: request.headers.set('calling-entity', 'UI'),
    });

    if (!request.headers.has('Content-Type')) {
      if (!request.headers.has('isUpload')) {
         request = request.clone({
           headers: request.headers.set('Content-Type', 'application/json'),
         });
      }
    }

    if (
      request.params instanceof CustomHttpParams &&
      request.params.etagValue
    ) {
      request = request.clone({
        setHeaders: {
          'If-Match': request.params.etagValue,
        },
      });
    }

    return next.handle(request).pipe();
  }
}

export class CustomHttpParams extends HttpParams {
  constructor(public etagValue: any) {
    super();
  }
}

After commenting out the part where Content-Type was being set, it worked as expected.

  • Related