I followed these steps to post a PDF file to Drupal.
Enable JSON API to create at admin/config/services/jsonapi
Enable the media POST resource
Media /media/{media}/edit: GET, PATCH, DELETE
/entity/media: POST
Methods: POST, formats: json: authentication: cookie
Create an Angular application and embed it in the Drupal page as a block
The Angular application captures the page using jspdf and embeds the image in the PDF. Instead of using an interceptor, I'm getting a token with each request.
this.certificateService.getCsrf().subscribe(token => {
this.certificateService.uploadMedia(pdf.output('blob'), fileName, token).subscribe({
complete: () => {
console.log('posted media');
}
})
});
getCsrf()
grabs the token as text.
getCsrf(): Observable<string> {
const headers = new HttpHeaders().set('Content-Type', 'text/plain; charset=utf-8');
return this.httpClient.get(this.host + '/session/token',{ headers: headers, responseType: 'text'}).pipe(catchError(this.handleError<any>('token')))
}
uploadMedia()
tries to post the PDF file to media to Drupal.
uploadMedia(pdf: Blob, name: string, token: string): Observable<any> {
const formData: FormData = new FormData();
formData.append('certificate', pdf, name);
const headers = new HttpHeaders();
headers.set('Accept', 'application/vnd.api+json');
headers.set('X-CSRF-Token',token);
// headers.set('Content-Type','application/octet-stream');
headers.set('Content-Type','application/hal+json');
headers.set('Content-Disposition',`file; filename=${name}`);
return this.httpClient.post(this.host + '/entity/media', formData, {headers: headers})
.pipe(
catchError(this.handleError<any>('uploadMedia'))
);
}
The error I get is 415 Unsupported media type.
I am using Angular 13 and Drupal 9.
I looked at the Drupal error log; it reports the following exception.
Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException: No route found that matches "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryYAzO5lNTfgLQwQq9" in Drupal\Core\Routing\ContentTypeHeaderMatcher->filter()
Just setting the Content-Type header does not always alter the content type header that is posted. e.g. With both axios and angular http if you post a FormData array you will get a multi-part form even though you the header at the call level is application/octet-stream
Using axios within angular as follows result in 'application/octet-stream':
import Axios from 'axios-observable';
Axios.defaults.headers.post['X-CSRF-Token'] = token;
return Axios.post(this.host + '/entity/media', pdf, {
headers: {
Accept: `application/json, text/plain, */*',
Authorization: `Basic ${basic}`,
Cookie: ${cookie}; XDEBUG_SESSION=13681',
'Content-Type': 'application/octet-stream',
}
});
Passing raw PDF data results in 'application/pdf'
const headers = new HttpHeaders();
headers.set('Content-Type', 'application/octet-stream');
return this.httpClient.post(this.host + '/entity/media', pdf, {headers: headers})
.pipe(
catchError(this.handleError<any>('uploadMedia'))
);
Reviewing the rest services page admin/config/services/rest, the allowed content format is 'json'
Debugging confirms that symfony is checking this value
Trying a REST interface i.e. '/jsonapi/media/document/field_media_document/'
Yes there is a 'bin' content type requirement in web/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php which
// Add the content type format access check. This will enforce that all
// incoming requests can only use the 'application/octet-stream'
// Content-Type header.
The /entity/media JSON API request url does need a JSON request as per web/core/lib/Drupal/Core/Routing/ContentTypeHeaderMatcher.php