[PYTHON] Création d'un mécanisme pour télécharger un gros fichier zip avec une authentification de base avec aws

Contenu de l'article en trois lignes

Aperçu

Lors du partage d'un fichier volumineux, si le courrier dépasse environ 8 Mo, il sera bloqué car il s'agit d'un serveur de messagerie. Même s'il n'est pas bloqué, l'envoi d'un gros fichier par courrier peut être assez ennuyeux, comme mettre une charge sur le serveur. Donc, je souhaite utiliser le service de stockage, mais s'il s'agit d'un vol de fichier Giga, je suis susceptible de me mettre en colère contre l'accord de sécurité (je l'ai beaucoup utilisé par le passé), et Google Drive s'inquiète du partage sans compte Google (accès si je ne connais pas l'URL Bien que ce ne soit pas possible, il est difficile pour tout le monde d'y accéder).

Tout ce que je veux, c'est une URL qui me permet d'appliquer temporairement un identifiant et de passer et de partager un gros fichier ... (supprimez-le après la fin ou rendez-le privé une fois le délai écoulé)

Donc, j'ai récemment reçu SAA de aws, et je me suis mis au défi de créer un mécanisme pour partager facilement des fichiers avec aws en tant que critique, ce qui était la raison de cet article.

Personnellement, je ne voulais pas allumer / éteindre le serveur ou le surveiller, alors je me suis demandé si je pouvais le construire sans serveur.

À propos, la taille du fichier que vous souhaitez partager est un fichier zip d'environ 100 Mo.

Faites équipe avec API Gateway, Lambda et S3

Chaque rôle de chaque service

API Gateway

Pour publier Lambda et appliquer l'authentification de base à l'URL publiée. Après vérification, API Gateway dispose d'une méthode pour s'authentifier avec une clé d'accès et une fonction appelée «API Gateway Lambda Authorizer» qui peut préparer Lambda et contrôler l'accès à l'API qui l'utilise.

Lambda

Préparez une fonction Lambda qui applique l'authentification de base pour utiliser «API Gateway Lambda Authorizer» et préparez deux pages pour le téléchargement du fichier zip après l'authentification de base avec la fonction Lambda.

La première consiste à afficher du HTML et à leur demander de sélectionner le fichier à télécharger. Au début, je pensais que ce serait bien de télécharger si je marchais sur l'URL, mais que se passe-t-il s'il y a plusieurs fichiers? J'ai donc décidé de préparer une page HTML. (Quand j'y pense maintenant, je suis content d'avoir utilisé le paramètre de requête. Oui ... → J'ai pensé, mais cela n'a pas fonctionné. Voir ci-dessous.)

L'autre est une fonction Lambda qui télécharge des fichiers depuis S3 et renvoie un fichier zip (binaire).

S3

Utilisez-le comme emplacement pour stocker les fichiers zip que vous souhaitez télécharger et comme emplacement pour stocker les modèles HTML.

Diagramme

TODO: créer et coller un diagramme

Mise en œuvre / configuration

Lambda

Nom de la fonction: download ___ auth

lambda pour effectuer l'authentification de base

import json
import os
import base64


def lambda_handler(event, context):
	policy = {
        'principalId': 'user',
        'policyDocument': {
          'Version': '2012-10-17',
          'Statement': [
            {
              'Action': 'execute-api:Invoke',
              'Effect': 'Deny',
              'Resource': event['methodArn']
            }
          ]
        }
    }

    if not basic_auth(event):
        print('Auth Error!!!')
        return policy
        
    policy['policyDocument']['Statement'][0]['Effect'] = 'Allow'
    return policy
    

def basic_auth(event):
    if 'headers' in event.keys() and 'authorization' in event['headers'].keys():
        auth_header = event['headers']['authorization']
    
    	#Obtenir des informations à partir des variables d'environnement lambda.
        user = os.environ['USER']
        password = os.environ['PASSWORD']
        print(os.environ)
        
        _b64 = base64.b64encode('{}:{}'.format(user, password).encode('utf-8'))
        auth_str = 'Basic {}'.format(_b64.decode('utf-8'))
        return auth_header == auth_str

    raise Exception('Auth Error!!!')

Nom de la fonction: download ___ index

Un lambda qui affiche les fichiers téléchargeables en HTML. Le modèle HTML est obtenu à partir de S3. Le moteur de modèle est jinja.

from jinja2 import Template
import boto3
from botocore.exceptions import ClientError

import os
import logging


logger = logging.getLogger()
S3 = boto3.resource('s3')
TEMPLATE_AWS_S3_BUCKET_NAME = 'hogehoge-downloader'
BUCKET = S3.Bucket(TEMPLATE_AWS_S3_BUCKET_NAME)


def get_object(bucket, object_name):
    """Retrieve an object from an Amazon S3 bucket

    :param bucket_name: string
    :param object_name: string
    :return: botocore.response.StreamingBody object. If error, return None.
    """
    try:
        response = bucket.Object(object_name).get()
    except ClientError as e:
        # AllAccessDisabled error == bucket or object not found
        logging.error(e)
        return None
    # Return an open StreamingBody object
    return response['Body'].read()



def main():
    index_html = get_object(BUCKET,
                            os.path.join('template', 'index.html')) \
                            .decode('utf8')
    li_html = get_object(BUCKET,
                          os.path.join('template', 'file_li.html')) \
                          .decode('utf8')

    index_t = Template(index_html)
    insert_list = []
    objs = BUCKET.meta.client.list_objects_v2(Bucket=BUCKET.name,
                                              Prefix='files')
    for obj in objs.get('Contents'):
        k = obj.get('Key')
        ks = k.split('/')
        if ks[1] == '':
            continue

        file_name = ks[1]
        print(obj.get('Key'))
        li_t = Template(li_html)
        insert_list.append(li_t.render(
            file_url='#',
            file_name=file_name
        ))

    output_html = index_t.render(file_li=''.join(insert_list))
    return output_html


def lambda_handler(event, context):
    output_html = main()
    
    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": 'text/html'
        },
        "isBase64Encoded": False,
        "body": output_html
    }

Nom de la fonction: download ___ download

Télécharger le fichier depuis s3 lambda

import boto3
from botocore.exceptions import ClientError

import os
import logging
import base64

logger = logging.getLogger()
S3 = boto3.resource('s3')
TEMPLATE_AWS_S3_BUCKET_NAME = 'hogehoge-downloader'
TEMPLATE_BUCKET = S3.Bucket(TEMPLATE_AWS_S3_BUCKET_NAME)


def get_object(bucket, object_name):
    """Retrieve an object from an Amazon S3 bucket

    :param bucket_name: string
    :param object_name: string
    :return: botocore.response.StreamingBody object. If error, return None.
    """
    try:
        response = bucket.Object(object_name).get()
    except ClientError as e:
        # AllAccessDisabled error == bucket or object not found
        logging.error(e)
        return None
    # Return an open StreamingBody object
    return response['Body'].read()


def lambda_handler(event, context):
    file_name = event['queryStringParameters']['fileName']
    body = get_object(TEMPLATE_BUCKET, os.path.join('files', file_name))
    return {
        "statusCode": 200,
        "headers": {
            "Content-Disposition": 'attachment;filename="{}"'.format(file_name),
            "Content-Type": 'application/zip'
        },
        "isBase64Encoded": True,
        "body": base64.b64encode(body).decode('utf-8')
    }

API Gateway

Construction initiale

--Sélectionnez l'API REST comme type d'API

Construit comme ça.

image.png

Paramètres d'autorisation

Défini pour fonctionner avec la fonction lambda qui effectue l'authentification de base.

image.png

Réglez comme suit. Veillez à décocher le cache d'autorisation. (Si vous le laissez coché et définissez plusieurs ressources, il se comportera étrangement)

スクリーンショット 2020-03-15 11.41.06.png

Ceci termine les paramètres d'authentification. Le reste est OK si vous le définissez avec chaque méthode.

Créer une méthode de ressource

Préparez une méthode de ressources comme indiqué ci-dessous.

image.png

Les paramètres détaillés pour chacun seront expliqués ci-dessous.

/html

Cochez Utiliser l'intégration du proxy lambda. En l'insérant, l'en-tête, etc. peut être défini du côté lambda.

image.png

Définissez la demande de méthode pour s'authentifier avec l'authentification de base.

image.png

Modifié pour que le HTML puisse être reconnu dans la réponse de la méthode. Modifiez le type de contenu du corps de la réponse en «text / html».

image.png

Ceci termine le réglage.

/download

Comme pour le côté HTML, cochez Utiliser l'intégration du proxy lambda.

image.png

L'authentification de base est également définie ici.

De plus, comme je souhaite recevoir le nom de fichier à télécharger en tant que paramètre, j'ai défini un paramètre nommé fileName dans le paramètre de chaîne de requête URL. (Si vous voulez le rendre obligatoire, cochez-le)

image.png

Modification du type de contenu de la réponse de la méthode car je souhaite télécharger le fichier zip.

image.png

Enfin, ajoutez le type de média binaire dans les paramètres du menu de gauche. En définissant ici le type de contenu cible, la valeur de retour (base64) de lambda sera convertie en binaire. (Cependant, vous devez ajouter Content-type à l'en-tête de la requête ou ʻapplication / zip` pour Accepter)

image.png

S3

nom du bucket: Créez hogehoge-downloader et créez le répertoire suivant.

--files: stocker les fichiers zip --template: Préparer le fichier de modèle HTML

Le modèle HTML est ci-dessous.

index.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Téléchargeur</title>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
      <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
   </head>
   <body>
      <section class="section">
         <div class="container">
            <div class="container">
               <ul>{{ file_li }}</ul>
         </div>
         </div>
      </section>
   </body>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
      function download(e) {
        const zipUrl = 'https://xxx.aws.com/prod/download?fileName=' + this.filename;
        const blob = axios.get(zipUrl, {
            responseType: 'blob',
            headers: {
                Accept: 'application/zip'
            },
        }).then(response => {
            window.URL = window.URL || window.webkitURL;
            const uri = window.URL.createObjectURL(response.data);
            const link = document.createElement('a');
            link.download = this.filename;
            link.href = uri;
            link.click()
        }).catch(error => {
            console.log(error);
        });
    }

    var links = Array.from(document.getElementsByClassName('downloadLink'));
    links.map(l => l.addEventListener('click', {filename: l.dataset.filename, handleEvent: download}));
  </script>
</html>

file_li.html

<li>
	<a href="{{ file_url }}" class="downloadLink" data-filename="{{ file_name }}">{{ file_name }}</a>
</li>

J'étais accro à

problème

Cependant, cette configuration ne fonctionne pas. Je n'ai pas remarqué jusqu'à ce que je me rassemble. .. ..

Je n'ai pas remarqué jusqu'à ce que je me sois réellement assemblé et confirmé l'erreur.

Veuillez consulter cette page.

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/limits.html

Charge utile d'appel (demande et réponse) 6 Mo (synchrone)

Quoi! !! ?? ?? ?? !! !! 6 Mo? ?? ?? !! !! ?? ?? ??

Dead end.

~ Complete ~ </ FONT>

Construire avec EC2

Oui

Diagramme

Seulement EC2. Préparé pour Ubuntu 18.04.

Mise en œuvre / configuration

Nous avons préparé l'API Bottle dans le conteneur Python préparé par Docker. Le processus d'authentification de base et de téléchargement du fichier zip est laissé à Bottle.

Téléchargez le fichier zip placé dans le répertoire courant.

app.py

import bottle


#Nom d'utilisateur et mot de passe d'authentification BASIC
USERNAME = "user"
PASSWORD = "pass"


def check(username, password):
    u"""
Vérifiez le nom d'utilisateur et le mot de passe pour l'authentification BASIC
    @bottle.auth_basic(check)Appliquer dans
    """
    return username == USERNAME and password == PASSWORD


@bottle.route("/zip")
@bottle.auth_basic(check)
def zip():
    zip_filename = 'files.zip'
    with open(zip_filename, 'rb') as f:
        body = f.read()

    response.content_type = 'application/zip'
    response.set_header('Content-Disposition', 'attachment; filename="{}"'.format(zip_filename))
    response.set_header('Content-Length', len(body))
    response.body = body
    return response


if __name__ == '__main__':
    bottle.run(host='0.0.0.0', port=80, debug=True)
$ docker run -p 80:80 -v $(pwd):/app -it docker-image-hogehoge python3 /app/app.py

Vous avez maintenant une URL à télécharger même avec une grande capacité (même si cela prend du temps)! !! !! !! !! !! !!

Impressions diverses

Pour étudier! C'est sans serveur! J'étais enthousiaste, mais j'aurais dû le faire avec EC2 depuis le début. .. .. .. .. .. ..

Recommended Posts

Création d'un mécanisme pour télécharger un gros fichier zip avec une authentification de base avec aws
Créer un gros fichier texte avec shellscript
Comment lire un fichier CSV avec Python 2/3
Comment déguiser un fichier ZIP en fichier PNG
Enregistrer l'objet dans un fichier avec pickle
AWS Step Functions pour apprendre avec un exemple
Je veux écrire dans un fichier avec Python
Convertir un fichier texte avec des valeurs hexadécimales en fichier binaire
J'ai essayé de charger / télécharger des fichiers sur AWS S3 / Azure BlobStorage / GCP CloudStorage avec Python
Comment mettre un lien hypertexte vers "file: // hogehoge" avec sphinx-> pdf
Je viens de créer un environnement virtuel avec la couche AWS lambda