Score:0

How do I post a PDF?

km flag

I followed these steps to post a PDF file to Drupal.

  1. Enable JSON API to create at admin/config/services/jsonapi

  2. Enable the media POST resource

    Media /media/{media}/edit: GET, PATCH, DELETE
          /entity/media: POST
    
    Methods: POST, formats: json: authentication: cookie
    
  3. 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.

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

Symfony accept JSON

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

entity media json request

Score:0
cn flag

I use Angular, not React, but I'm pretty sure this part is wrong:

  // headers.set('Content-Type','application/octet-stream');
  headers.set('Content-Type','application/hal+json');

Files need to be posted as application/octet-stream. It's not a JSON object; it's a file.

Also, you may need to massage the PDF data into something that JSON:API understands. For example, here's the docs on doing this with axios/Node.js.

In my case, for image files, I had to read the file in as a blob (React code, but may provide an example):

// https://stackoverflow.com/a/46568146
function readFileBlob(fileBlob: Blob) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = () => {
      resolve(reader.result);
    };
    reader.onabort = () => Promise.reject(Error('file reading was aborted'));
    reader.onerror = () => Promise.reject(Error('file reading has failed'));
    reader.readAsArrayBuffer(fileBlob);
  });
}

// fileToPost is a string that contains a blob:
// blob:http://localhost:8100/00507d3e-7f6f-4f48-be80-b55c8bc20dd3
const postAuthFileBlob = async (
  fetchUrl: string,
  fileToPost: string,
  // Fetch might return anything.
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> => {
  const fileBlob = await fetch(fileToPost).then((res) => res.blob());
  const arrayStr = (await readFileBlob(fileBlob)) as ArrayBuffer;
  return postAuthFileBlobPart2(fetchUrl, arrayStr);
};
Interlated avatar
km flag
I think you are close. The back-end is complaining that the content-type is multipart/form-data instead of application/octet-stream
Interlated avatar
km flag
Just posting the pdf gives application/pdf return this.httpClient.post(this.host + '/entity/media', pdf, {headers: headers}). Still drupal cannot find a match for application/pdf. No route found that matches "Content-Type: application/pdf" in Drupal\Core\Routing\ContentTypeHeaderMatcher->filter() (line 49 of
cn flag
@Interlated As I already stated, you need to post as `application/octet-stream`. For some reason you commented that out in your code, but you have to post as `application/octet-stream` if you want to upload any binary files.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.