Angular 8 fails to read response from a request if there was a large blob in it

2020-02-14 javascript angular nginx flask

I'm developing an API that has an endpoint to update a table with a large blob (15MB or so), this api updates the table then responds with a JSON.

When this endpoint receives a small blob (a few bytes), angular can read the JSON and show the result to the user. If this endpoint receives a large blob, it seems the whole request fails, as firefox shows in the console: enter image description here

I don't think it is a CORS problem because all other requests I make to this server are OK, even this request is OK if the blob is small enough.

I am usgin Angular 8, Python 3 (with Flask) and Mysql, but I don't think it is a problem with the database or server side configuration (for example max_allowed_packet or innodb_log_file_size for mysql and client_max_body_size for nginx) because the server receives those exact same files in the insert endpoint and everything is fine. If I save a 15MB file it is successful, but if I try to update that file with another file with the exact same size, then the error appears.

Another thing that makes me believe the problem is not on the server side is that nginx access log shows only successful requests (all responded with code 200)

So I think the problem can only be Angular, but since it works if the blob is small, I have absolutely no idea where the problem could be.

Here is the service that makes the request: (insert works fine with all sizes of blobs, update only with small blobs)

export class documentService {

    constructor(private http: HttpClient, private toastr: ToastrService) { }

    insert(file:File, tags:Tag[], permitions:User[], extraFields:any, type:string){
        const formData = new FormData();
        formData.append('tags', JSON.stringify(tags));
        formData.append('permitions', JSON.stringify(permitions));
        formData.append('binary_data', file);
        formData.append('extraFields', JSON.stringify(extraFields))

        return this.http.post<Resposta>(SERVER+"/document/"+type, formData, {"reportProgress": true})
    }

    update(file:File, tags:Tag[], permitions:Usuario[], docId, extraFields:any, type:string){
        const formData = new FormData();
        formData.append('tags', JSON.stringify(tags));
        formData.append('permitions', JSON.stringify(permitions));
        formData.append('binary_data', file);
        formData.append('extraFields', JSON.stringify(extraFields))
        return this.http.put<Resposta>(SERVER+"/document/"+type+"/"+docId, formData, {"reportProgress": true})
    }
}

here is the part of the component that uses the above service:

this.docService.update(file, tags, permitions, this.doc.id,this.doc, TYPES.INTERNAL).subscribe( r => {
    if(!r.error){
        this.setEditMode(false)
        this.hide()
    }
})

And I also have an interceptor:

export class ApiInterceptor implements HttpInterceptor {
    constructor(private toastr: ToastrService, private progress: ShowProgressService) {}

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        let newHeaders = { 'Authorization': localStorage.getItem('jwt') || ""}
        const cloneReq = req.clone({
            setHeaders: newHeaders
        });


        return next.handle(cloneReq).pipe(
            tap((event: any)=>{
                if(event.body && event.body.msg && event.body.error!=undefined){
                    if(event.body.error){
                        this.toastr.error(event.body.msg)
                    } else{
                        this.toastr.success(event.body.msg)
                    }
                }

                // shows upload progress
                if(event.type === HttpEventType.UploadProgress){
                    let percentDone = Math.round((100 * event.loaded) / event.total);
                    this.progress.showUpload(percentDone)

                }

                if(event.type === HttpEventType.DownloadProgress){
                    let percentDone = Math.round((100 * event.loaded) / event.total);
                    this.progress.showDownload(percentDone)
                }

            })
        );
    }
}

Edit 1 - OPTIONS Request:

Request:

Host: url
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: */*
Accept-Language: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization
Referer: http://url/dashboard
Origin: http://url
DNT: 1
Connection: keep-alive

Response:

HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Thu, 13 Feb 2020 20:30:39 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: keep-alive
Allow: GET, HEAD, PUT, OPTIONS, DELETE
Access-Control-Allow-Origin: http://url
Vary: Origin
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT

Answers

Based on your OPTIONS response added, it looks like you're not correctly setting the Access-Control-Allow-Origin header in the server's response to the CORS OPTIONS request.

Try setting Access-Control-Allow-Origin: * in your server's response to see if CORS is the issue, then replace the * wildcard with the appropriate origin you'll be using to prevent potentially malicious cross origin requests

Related