Análise de dados de várias partes/de formulário HTTP

Função do Cloud que analisa solicitações de formulário HTTP.

Páginas de documentação que incluem esta amostra de código-

Para visualizar o exemplo de código usado em contexto, consulte a seguinte documentação:

Amostra de código

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;

namespace HttpFormData
{
    public class Function : IHttpFunction
    {
        private readonly ILogger _logger;

        public Function(ILogger<Function> logger) =>
            _logger = logger;

        public async Task HandleAsync(HttpContext context)
        {
            HttpResponse response = context.Response;
            HttpRequest request = context.Request;

            if (request.Method != "POST")
            {
                response.StatusCode = (int) HttpStatusCode.MethodNotAllowed;
                return;
            }

            // This code will process each file uploaded.
            string tempDirectory = Path.GetTempPath();
            foreach (IFormFile file in request.Form.Files)
            {
                if (string.IsNullOrEmpty(file.FileName))
                {
                    continue;
                }
                _logger.LogInformation("Processed file: {file}", file.FileName);

                // Note: GCF's temp directory is an in-memory file system
                // Thus, any files in it must fit in the instance's memory.
                string outputPath = Path.Combine(tempDirectory, file.FileName);

                // Note: files saved to a GCF instance itself may not persist across executions.
                // Persistent files should be stored elsewhere, e.g. a Cloud Storage bucket.
                using (FileStream output = File.Create(outputPath))
                {
                    await file.CopyToAsync(output);
                }

                // TODO(developer): process saved files here
                File.Delete(outputPath);
            }

            // This code will process other form fields.
            foreach (KeyValuePair<string, StringValues> parameter in request.Form)
            {
                // TODO(developer): process field values here
                _logger.LogInformation("Processed field '{key}' (value: '{value}')",
                    parameter.Key, (string) parameter.Value);
            }
        }
    }
}

C++

#include <google/cloud/functions/http_request.h>
#include <google/cloud/functions/http_response.h>
#include <absl/strings/str_split.h>
#include <absl/strings/string_view.h>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <functional>
#include <regex>
#include <vector>

namespace gcf = ::google::cloud::functions;

namespace {
class FormDataDelimiter {
 public:
#if defined(__APPLE__) && defined(__clang__)
  using searcher = std::default_searcher<absl::string_view::iterator>;
#else
  using searcher =
      std::boyer_moore_horspool_searcher<absl::string_view::iterator>;
#endif  // ! __APPLE__ && __clang__
  static FormDataDelimiter FromHeader(std::string const& header);
  [[nodiscard]] absl::string_view Find(absl::string_view text,
                                       std::size_t pos) const;

 private:
  explicit FormDataDelimiter(std::string body, std::string part)
      : body_marker_(std::move(body)),
        body_marker_view_(body_marker_),
        body_marker_searcher_(body_marker_view_.begin(),
                              body_marker_view_.end()),
        part_marker_(std::move(part)),
        part_marker_view_(part_marker_),
        part_marker_searcher_(part_marker_view_.begin(),
                              part_marker_view_.end()) {}

  std::string body_marker_;
  absl::string_view body_marker_view_;
  searcher body_marker_searcher_;
  std::string part_marker_;
  absl::string_view part_marker_view_;
  searcher part_marker_searcher_;
};

}  // namespace

gcf::HttpResponse http_form_data(gcf::HttpRequest request) {
  if (request.verb() != "POST") {
    return gcf::HttpResponse{}.set_result(gcf::HttpResponse::kMethodNotAllowed);
  }
  auto const header = request.headers().find("content-type");
  if (header == request.headers().end()) {
    return gcf::HttpResponse{}.set_result(gcf::HttpResponse::kBadRequest);
  }
  auto const& content_type = header->second;
  if (content_type.rfind("multipart/form-data", 0) != 0) {
    return gcf::HttpResponse{}.set_result(gcf::HttpResponse::kBadRequest);
  }
  auto delimiter = FormDataDelimiter::FromHeader(header->second);

  auto payload = std::move(request).payload();
  std::vector<absl::string_view> parts =
      absl::StrSplit(payload, delimiter, absl::SkipEmpty{});
  nlohmann::json result{{"parts", std::vector<nlohmann::json>{}}};
  for (auto& p : parts) {
    std::vector<absl::string_view> components =
        absl::StrSplit(p, absl::MaxSplits("\r\n\r\n", 2));
    auto const body_size =
        components.size() == 2 ? components[0].size() : std::size_t(0);

    std::vector<std::string> part_headers =
        absl::StrSplit(components[0], "\r\n");
    nlohmann::json descriptor{{"bodySize", body_size},
                              {"headerCount", part_headers.size()}};
    for (auto const& h : part_headers) {
      static auto const kContentDispositionRE = std::regex(
          R"re(^content-disposition:)re",
          std::regex::extended | std::regex::icase | std::regex::optimize);
      static auto const kNameRE =
          std::regex(R"re([;[:space:]]name=([^[:space:];]+))re",
                     std::regex::extended | std::regex::icase);
      // NOTE: this does not handle embedded spaces and/or semi-colons in the
      // filename, and so should be improved for production code.
      static auto const kFilenameRE =
          std::regex(R"re([;[:space:]]filename=([^[:space:];]+))re",
                     std::regex::extended | std::regex::icase);

      if (!std::regex_search(h.begin(), h.end(), kContentDispositionRE)) {
        continue;
      }
      std::smatch m;
      if (std::regex_search(h.begin(), h.end(), m, kNameRE)) {
        descriptor["name"] = m[1];
      }

      if (!std::regex_search(h.begin(), h.end(), m, kFilenameRE)) continue;
      descriptor["isFile"] = true;
      descriptor["filename"] = m[1];
    }
    result["parts"].push_back(std::move(descriptor));
  }

  return gcf::HttpResponse{}
      .set_header("content-type", "application/json")
      .set_payload(result.dump());
}

Go


// Package http provides a set of HTTP Cloud Functions samples.
package http

import (
	"fmt"
	"log"
	"net/http"
)

// UploadFile processes a 'multipart/form-data' upload request.
func UploadFile(w http.ResponseWriter, r *http.Request) {
	const maxMemory = 2 * 1024 * 1024 // 2 megabytes.

	// ParseMultipartForm parses a request body as multipart/form-data.
	// The whole request body is parsed and up to a total of maxMemory bytes of
	// its file parts are stored in memory, with the remainder stored on
	// disk in temporary files.

	// Note that any files saved during a particular invocation may not
	// persist after the current invocation completes; persistent files
	// should be stored elsewhere, such as in a Cloud Storage bucket.
	if err := r.ParseMultipartForm(maxMemory); err != nil {
		http.Error(w, "Unable to parse form", http.StatusBadRequest)
		log.Printf("Error parsing form: %v", err)
		return
	}

	// Be sure to remove all temporary files after your function is finished.
	defer func() {
		if err := r.MultipartForm.RemoveAll(); err != nil {
			http.Error(w, "Error cleaning up form files", http.StatusInternalServerError)
			log.Printf("Error cleaning up form files: %v", err)
		}
	}()

	// r.MultipartForm.File contains *multipart.FileHeader values for every
	// file in the form. You can access the file contents using
	// *multipart.FileHeader's Open method.
	for _, headers := range r.MultipartForm.File {
		for _, h := range headers {
			fmt.Fprintf(w, "File uploaded: %q (%v bytes)", h.Filename, h.Size)
			// Use h.Open() to read the contents of the file.
		}
	}

}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.logging.Logger;

public class HttpFormData implements HttpFunction {
  private static final Logger logger = Logger.getLogger(HttpFormData.class.getName());

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {

    if (!"POST".equals(request.getMethod())) {
      response.setStatusCode(HttpURLConnection.HTTP_BAD_METHOD);
      return;
    }

    // This code will process each file uploaded.
    String tempDirectory = System.getProperty("java.io.tmpdir");
    for (HttpRequest.HttpPart httpPart : request.getParts().values()) {
      String filename = httpPart.getFileName().orElse(null);
      if (filename == null) {
        continue;
      }

      logger.info("Processed file: " + filename);

      // Note: GCF's temp directory is an in-memory file system
      // Thus, any files in it must fit in the instance's memory.
      Path filePath = Paths.get(tempDirectory, filename).toAbsolutePath();

      // Note: files saved to a GCF instance itself may not persist across executions.
      // Persistent files should be stored elsewhere, e.g. a Cloud Storage bucket.
      Files.copy(httpPart.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);

      // TODO(developer): process saved files here
      Files.delete(filePath);
    }

    // This code will process other form fields.
    request.getQueryParameters().forEach(
        (fieldName, fieldValues) -> {
          String firstFieldValue = fieldValues.get(0);

          // TODO(developer): process field values here
          logger.info(String.format(
              "Processed field: %s (value: %s)", fieldName, firstFieldValue));
        });
  }
}

Node.js

/**
 * Parses a 'multipart/form-data' upload request
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
const path = require('path');
const os = require('os');
const fs = require('fs');

// Node.js doesn't have a built-in multipart/form-data parsing library.
// Instead, we can use the 'busboy' library from NPM to parse these requests.
const Busboy = require('busboy');

exports.uploadFile = (req, res) => {
  if (req.method !== 'POST') {
    // Return a "method not allowed" error
    return res.status(405).end();
  }
  const busboy = new Busboy({headers: req.headers});
  const tmpdir = os.tmpdir();

  // This object will accumulate all the fields, keyed by their name
  const fields = {};

  // This object will accumulate all the uploaded files, keyed by their name.
  const uploads = {};

  // This code will process each non-file field in the form.
  busboy.on('field', (fieldname, val) => {
    /**
     *  TODO(developer): Process submitted field values here
     */
    console.log(`Processed field ${fieldname}: ${val}.`);
    fields[fieldname] = val;
  });

  const fileWrites = [];

  // This code will process each file uploaded.
  busboy.on('file', (fieldname, file, filename) => {
    // Note: os.tmpdir() points to an in-memory file system on GCF
    // Thus, any files in it must fit in the instance's memory.
    console.log(`Processed file ${filename}`);
    const filepath = path.join(tmpdir, filename);
    uploads[fieldname] = filepath;

    const writeStream = fs.createWriteStream(filepath);
    file.pipe(writeStream);

    // File was processed by Busboy; wait for it to be written.
    // Note: GCF may not persist saved files across invocations.
    // Persistent files must be kept in other locations
    // (such as Cloud Storage buckets).
    const promise = new Promise((resolve, reject) => {
      file.on('end', () => {
        writeStream.end();
      });
      writeStream.on('finish', resolve);
      writeStream.on('error', reject);
    });
    fileWrites.push(promise);
  });

  // Triggered once all uploaded files are processed by Busboy.
  // We still need to wait for the disk writes (saves) to complete.
  busboy.on('finish', async () => {
    await Promise.all(fileWrites);

    /**
     * TODO(developer): Process saved files here
     */
    for (const file in uploads) {
      fs.unlinkSync(uploads[file]);
    }
    res.send();
  });

  busboy.end(req.rawBody);
};

PHP


use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Psr7\Response;

function uploadFile(ServerRequestInterface $request): ResponseInterface
{
    if ($request->getMethod() != 'POST') {
        return new Response(405, [], 'Method Not Allowed: expected POST, found ' . $request->getMethod());
    }

    $contentType = $request->getHeader('Content-Type')[0];
    if (strpos($contentType, 'multipart/form-data') !== 0) {
        return new Response(400, [], 'Bad Request: content of type "multipart/form-data" not provided, found ' . $contentType);
    }

    $fileList = [];
    /** @var $file Psr\Http\Message\UploadedFileInterface */
    foreach ($request->getUploadedFiles() as $name => $file) {
        // Use caution when trusting the client-provided filename:
        // https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload
        $fileList[] = $file->getClientFilename();

        infoLog('Processing ' . $file->getClientFilename());
        $filename = tempnam(sys_get_temp_dir(), $name . '.') . '-' . $file->getClientFilename();

        // Use $file->getStream() to process the file contents in ways other than a direct "file save".
        infoLog('Saving to ' . $filename);
        $file->moveTo($filename);
    }

    if (empty($fileList)) {
        $msg = 'Bad Request: no files sent for upload';
        errorLog($msg);
        return new Response(400, [], $msg);
    }

    return new Response(201, [], 'Saved ' . join(', ', $fileList));
}

function errorLog($msg): void
{
    $stream = fopen('php://stderr', 'wb');
    $entry = json_encode(['msg' => $msg, 'severity' => 'error'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    fwrite($stream, $entry . PHP_EOL);
}

function infoLog($msg): void
{
    $stream = fopen('php://stderr', 'wb');
    $entry = json_encode(['message' => $msg, 'severity' => 'info'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    fwrite($stream, $entry . PHP_EOL);
}

Python

import os
import tempfile
from werkzeug.utils import secure_filename

# Helper function that computes the filepath to save files to
def get_file_path(filename):
    # Note: tempfile.gettempdir() points to an in-memory file system
    # on GCF. Thus, any files in it must fit in the instance's memory.
    file_name = secure_filename(filename)
    return os.path.join(tempfile.gettempdir(), file_name)

def parse_multipart(request):
    """ Parses a 'multipart/form-data' upload request
    Args:
        request (flask.Request): The request object.
    Returns:
        The response text, or any set of values that can be turned into a
         Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """

    # This code will process each non-file field in the form
    fields = {}
    data = request.form.to_dict()
    for field in data:
        fields[field] = data[field]
        print('Processed field: %s' % field)

    # This code will process each file uploaded
    files = request.files.to_dict()
    for file_name, file in files.items():
        # Note: GCF may not keep files saved locally between invocations.
        # If you want to preserve the uploaded files, you should save them
        # to another location (such as a Cloud Storage bucket).
        file.save(get_file_path(file_name))
        print('Processed file: %s' % file_name)

    # Clear temporary directory
    for file_name in files:
        file_path = get_file_path(file_name)
        os.remove(file_path)

    return "Done!"

Ruby

require "functions_framework"

FunctionsFramework.http "http_form_data" do |request|
  # The request parameter is a Rack::Request object.
  # See https://www.rubydoc.info/gems/rack/Rack/Request

  # This Rack call parses multipart form data, returning the params as a hash.
  # The returned params hash includes an entry for each field. File uploads
  # are written to Tempfiles and represented by hashes, while plain fields are
  # represented by strings.
  params = request.POST

  begin
    params.each do |name, part|
      if part.is_a? Hash
        # Handle a file upload part by logging the md5 hash.
        md5 = Digest::MD5.hexdigest part[:tempfile].read
        file_name = part[:filename]
        logger.info "Processed file=#{file_name} md5=#{md5}"
      else
        # Handle a non-file part by logging the value.
        logger.info "Processed field=#{name} value=#{part}"
      end
    end
  ensure
    # Ensure that all Tempfile objects are closed and deleted. The Cloud
    # Functions runtime keeps temporary files in an in-memory file system,
    # so to lower memory usage it is good practice to clean up Tempfiles
    # explicitly rather than wait for object finalization.
    params.each_value do |part|
      part[:tempfile].close! if part.is_a? Hash
    end
  end

  # The HTTP response body.
  "OK"
end

A seguir

Para pesquisar e filtrar exemplos de código de outros produtos do Google Cloud, consulte o navegador de exemplos do Google Cloud.