title: How do I use Signed URLs to upload to Google Storage bucket in Python?
body: <p>I am able to create signed URLs and just need to know what to do with them after they are created.</p>
  <p>There are several examples using Javascript to upload via a signed URL, but I cannot find any in Python. I am trying to use signed URLs as a workaround for the 32 MB limit imposed by Google App Engine for my Flask application.</p>
  <p>Here is my python app.py script (not full functionality of my app here, just trying to upload to my bucket successfully):</p>
  <pre><code>from flask import Flask, request, render_template
  from google.cloud import storage
  import pandas as pd
  import os
  import gcsfs
  
  bucket_name = &quot;my-bucket&quot;
  
  os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = '/path/to/file.json'
  
  app = Flask(__name__)
  
  def upload_blob(bucket_name, source_file_name, destination_blob_name):
      storage_client = storage.Client()
      bucket = storage_client.bucket(bucket_name)
      blob = bucket.blob(destination_blob_name)
      blob.upload_from_file(source_file_name)
  
      print(&quot;success&quot;)
  
  @app.route('/')
  def homepage():
      return render_template('home.html')
  
  @app.route('/', methods = ['GET', 'POST'])
  def upload_file():
      if request.method == 'POST':
          file1 = request.files['file1'] 
          file2 = request.files['file2']
          upload_blob(bucket_name, file1, 'file-1')
          upload_blob(bucket_name, file2, 'file-2')
          df = pd.read_csv('gs://' + bucket_name + '/' + 'file-1')
          print(df.shape)
          return &quot;done&quot;
  
  
  if __name__ == &quot;__main__&quot;:
    app.run(debug=True)
  
  </code></pre>
  <p>Here is the function I am using to create the signed URL:</p>
  <pre><code>def generate_upload_signed_url_v4(bucket_name, blob_name):
  
      storage_client = storage.Client()
      bucket = storage_client.bucket(bucket_name)
      blob = bucket.blob(blob_name)
  
      url = blob.generate_signed_url(
          version=&quot;v4&quot;,
          # This URL is valid for 15 minutes
          expiration=datetime.timedelta(minutes=15),
          # Allow GET requests using this URL.
          method=&quot;PUT&quot;,
          content_type=&quot;application/octet-stream&quot;,
      )
      print(url)
      return url
  
  generate_upload_signed_url_v4(bucket_name, 'file.csv')
  
  </code></pre>
  <p>And below is my home.html:</p>
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
     &lt;meta charset=&quot;UTF-8&quot;&gt;
     &lt;title&gt;test upload&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;h3&gt; test upload &lt;/h3&gt;
  
      &lt;form method=&quot;POST&quot; action=&quot;/&quot; enctype=&quot;multipart/form-data&quot;&gt;
          &lt;p&gt;Upload file1 below&lt;/p&gt;
          &lt;input type=&quot;file&quot; name=&quot;file1&quot;&gt; 
          &lt;br&gt;
          &lt;br&gt;
          &lt;p&gt;Upload file2 below&lt;/p&gt;
          &lt;input type=&quot;file&quot; name=&quot;file2&quot;&gt;
          &lt;br&gt;
          &lt;br&gt;
          &lt;input type=&quot;submit&quot; value=&quot;upload&quot;&gt;
      &lt;/form&gt;
  
  
  &lt;/body&gt;
  &lt;/html&gt;
  
  </code></pre>
  <p>Based on what I researched here is my CORS configuration for the bucket I am trying to upload to:</p>
  <pre><code>
  [
  {&quot;maxAgeSeconds&quot;: 3600, 
  &quot;method&quot;: [&quot;GET&quot;, &quot;PUT&quot;, &quot;POST&quot;], 
  &quot;origin&quot;: [&quot;https://my-app.uc.r.appspot.com&quot;, &quot;http://local.machine.XXXX/&quot;], 
  &quot;responseHeader&quot;: [&quot;Content-Type&quot;]}
  ]
  
  </code></pre>
  <p>Does the signed URL that is generated go in the html form? Does it need to go into my upload_file function?</p>
  <p>Finally, when I paste the signed URL into my browser it shows this error:</p>
  <pre><code>
  &lt;Error&gt;
  &lt;Code&gt;MalformedSecurityHeader&lt;/Code&gt;
  &lt;Message&gt;Your request has a malformed header.&lt;/Message&gt;
  &lt;ParameterName&gt;content-type&lt;/ParameterName&gt;
  &lt;Details&gt;Header was included in signedheaders, but not in the request.&lt;/Details&gt;
  &lt;/Error&gt;
  
  </code></pre>
  <p>This is my first SO question so I apologize if it is poorly constructed. I am super lost and new to GCP. I have searched SO for a while now, and not found a use-case with Python/Flask where I can see how the signed URL is incorporated into the file upload process.</p>
  <p>Again, I am building a webapp on Google App Engine flex, and need signed URLs to workaround the 32 MB file upload restriction.</p>
  <p><strong>UPDATE</strong></p>
  <p>I got the signed URL component figured out after realizing I needed to simply make a request to the signed URL.</p>
  <p>Below is my new script that is loaded in App Engine (imports and &quot;if <strong>name</strong> = <strong>main</strong>...&quot; removed for snippet below).</p>
  <pre><code>
  os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = '/path/to/file.json'
  
  EXPIRATION = datetime.timedelta(minutes=15)
  FILE_TYPE = 'text/csv'
  BUCKET = 'my-bucket'
  
  def upload_via_signed(bucket_name, blob_name, filename, expiration, file_type):
      bucket = storage.Client().get_bucket(bucket_name)
  
      blob = bucket.blob(blob_name)
  
      signed_url = blob.generate_signed_url(method='PUT', expiration=expiration, content_type=file_type)
  
      requests.put(signed_url, open(filename.filename, 'rb'), headers={'Content-Type': file_type})
  
  app = Flask(__name__)
  
  app.config['UPLOAD_FOLDER'] = '/tmp'
  
  @app.route('/')
  def homepage():
      return render_template('home.html')
  
  @app.route('/', methods = ['GET', 'POST'])
  def upload_file():
      if request.method == 'POST':
  
          diag = request.files['file']
          filename_1 = secure_filename(diag.filename)
          filepath_1 = os.path.join(app.config['UPLOAD_FOLDER'], filename_1)
          diag.save(filepath_1)
  
          person = request.files['person']
          filename_2 = secure_filename(person.filename)
          filepath_2 = os.path.join(app.config['UPLOAD_FOLDER'], filename_2)
          person.save(filepath_2)
  
          upload_via_signed(BUCKET, 'diag.csv', diag, EXPIRATION, FILE_TYPE)
  
          upload_via_signed(BUCKET, 'person.csv', person, EXPIRATION, FILE_TYPE)
  
          df_diag = pd.read_csv('gs://' + BUCKET + '/' + 'diag.csv')
          print(df_diag.shape)
          return &quot;done&quot;
  
  </code></pre>
  <p>The code above is still throwing the 413 entity too large error. I think it's because I've got the 'POST' going through App Engine even though I am creating signed URLs. How do I need to re-arrange/what am I doing wrong? How does the code need to be structured to have the user upload directly to Google Cloud Storage via the signed URLs and avoid triggering the 413 entity too large error?</p>