Azure基本

Azure App ServiceからAzure Functionsを呼び出す際の通信をString型でなくObjectのままで実施してみた

これまでこのブログで取り上げてきたサンプルプログラム内では、Azure App ServiceからAzure Functionsを呼び出す際に利用するRestTemplateを用いたAPI通信で、JSON文字列のString型で通信を行ってきたが、オブジェクトのままで通信を行うこともできる。

今回は、Azure App ServiceからAzure Functionsを呼び出す際のAPI通信で、オブジェクトのままで通信を行ってみたので、そのサンプルプログラムを共有する。

前提条件

下記記事の実装が完了していること。

Azure App ServiceとAzure Functionsの共通クラスを別プロジェクトに取り出してみたこれまでこのブログで取り上げてきたAzureのサンプルプログラムの中には、Azure App ServiceとAzure Functio...

作成したサンプルプログラム(共通クラス)の内容

作成したサンプルプログラム(共通クラス)の構成は以下の通り。
サンプルプログラムの構成(共通クラス)
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。

pom.xml(追加分)の内容は以下の通りで、JsonSerializeアノテーションを利用するためのjackson-databindを追加している。

<!-- JsonSerializeアノテーションを利用するための設定 -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.13.0</version>
</dependency>

また、ファイルダウンロードリストの戻り値に利用するFileDataクラスの内容は以下の通りで、RestTemplateを用いたAPI通信でオブジェクトのままで通信を行えるよう、byte配列をByte配列に変更している。

package com.example.model;

import lombok.Data;

@Data
public class FileData {

    /** ID */
    private int id;
	
    /** ファイル名 */
    private String fileName;
	
    /** ファイルパス */
    private String filePath;
	
    /** ファイルデータ */
    private Byte[] fileData;
	
}

さらに、ファイルアップロード時に利用するFileUploadParamクラスの内容は以下の通りで、ファイル名とByte配列をもつファイルデータを設定するようにしている。

package com.example.model;

import lombok.Data;

@Data
public class FileUploadParam {

    /** ファイル名 */
    private String fileName;
	
    /** ファイルデータ */
    private Byte[] fileData;
	
}

また、ファイルダウンロードリストを取得する際のGetFileListParamクラスの内容は以下の通りで、RestTemplateを用いたAPI通信でオブジェクトのままで通信を行えるよう、@JsonSerializeアノテーションを付与している。

package com.example.model;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import lombok.Data;

@Data
@JsonSerialize
public class GetFileListParam {

}

なお、@JsonSerializeアノテーションを付与しないと、「com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.example.model.GetFileListParam and no properties discovered to create BeanSerializer」というエラーが出るため、オブジェクトをJSON文字列に変換するための@JsonSerializeアノテーションが必要になる。

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-rest-template-object/demoAzureCommon



「DesignEvo」は多くのテンプレートからロゴを簡単に作成できるツールだった多くのテンプレートが用意されていてロゴを簡単に作成できるツールの一つに、「DesignEvo」があります。今回は、「DesignEvo」...

作成したサンプルプログラム(App Service側)の内容

作成したサンプルプログラム(App Service側)の構成は以下の通り。
サンプルプログラムの構成(AppService側)
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。

pom.xml(追加分)の内容は以下の通りで、ArrayUtilsクラスを含むcommons-lang3の設定を追加している。

<!-- ArrayUtilsクラスを含むcommons-lang3の設定 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
</dependency>

また、コントローラクラスの内容は以下の通りで、HttpEntityクラスにParamクラスのオブジェクトを設定し、RestTemplateを用いたAPI通信の戻り値をResultクラスに変更している。また、ファイルデータがbyte配列からByte配列に変わったため、ファイルデータの設定/取り出しを、ArrayUtilsクラスのメソッドを用いるよう変更している。

package com.example.demo;

import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.thymeleaf.util.StringUtils;

import com.example.model.FileData;
import com.example.model.FileUploadParam;
import com.example.model.FileUploadResult;
import com.example.model.GetFileListParam;
import com.example.model.GetFileListResult;

@Controller
public class DemoController {

    /** RestTemplateオブジェクト */
    @Autowired
    private RestTemplate restTemplate;

    /** application.propertiesからdemoAzureFunc.urlBaseの値を取得 */
    @Value("${demoAzureFunc.urlBase}")
    private String demoAzureFuncBase;

    /**
     * ファイルリストを取得し、メイン画面を初期表示する.
     * @param model   Modelオブジェクト
     * @param session HttpSessionオブジェクト
     * @return メイン画面
     */
    @GetMapping("/")
    public String index(Model model, HttpSession session) {
        // Azure FunctionsのgetFileList関数を呼び出すためのヘッダー情報を設定する
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        // Azure FunctionsのgetFileList関数を呼び出すための引数を設定する
        HttpEntity<GetFileListParam> entity 
            = new HttpEntity<>(new GetFileListParam(), headers);

        // Azure FunctionsのgetFileList関数を呼び出す
        ResponseEntity<GetFileListResult> response = restTemplate.exchange(
              demoAzureFuncBase + "getFileList", HttpMethod.POST
            , new HttpEntity<>(entity), GetFileListResult.class);
        GetFileListResult getFileListResult = response.getBody();

        model.addAttribute("fileDataList", getFileListResult.getFileDataList());
        session.setAttribute("fileDataList", getFileListResult.getFileDataList());

        model.addAttribute("message"
            , "アップロードするファイルを指定し、アップロードボタンを押下してください。");
        return "main";
    }

    /**
     * ファイルダウンロード処理.
     * @param id       ID
     * @param response HttpServletResponse
     * @param session  HttpSessionオブジェクト
     * @return 画面遷移先(nullを返す)
     */
    @RequestMapping("/download")
    public String download(@RequestParam("id") String id
           , HttpServletResponse response, HttpSession session) {
        // セッションからファイルリストを取得する
        @SuppressWarnings("unchecked")
        ArrayList<FileData> fileDataList 
            = (ArrayList<FileData>) session.getAttribute("fileDataList");
        FileData fileData = fileDataList.get(Integer.parseInt(id) - 1);

        // ファイルダウンロードの設定を実施
        // ファイルの種類は指定しない
        response.setContentType("application/octet-stream");
        response.setHeader("Cache-Control", "private");
        response.setHeader("Pragma", "");
        response.setHeader("Content-Disposition", "attachment;filename=\"" 
            + getFileNameEncoded(fileData.getFileName()) + "\"");

        // ダウンロードファイルへ出力
        try (OutputStream out = response.getOutputStream()) {
            out.write(ArrayUtils.toPrimitive(fileData.getFileData()));
            out.flush();
        } catch (Exception e) {
            System.err.println(e);
        }

        // 画面遷移先はnullを指定
        return null;
    }

    /**
     * ファイルデータをAzure Blob Storageに登録する.
     * @param uploadFile         アップロードファイル
     * @param model              Modelオブジェクト
     * @param redirectAttributes リダイレクト先に渡すパラメータ
     * @return メイン画面
     */
    @PostMapping("/upload")
    public String add(@RequestParam("upload_file") MultipartFile uploadFile
          , Model model, RedirectAttributes redirectAttributes) {
        // ファイルが未指定の場合はエラーとする
        if (uploadFile == null || StringUtils.isEmptyOrWhitespace(
                uploadFile.getOriginalFilename())) {
            redirectAttributes.addFlashAttribute("errMessage"
                , "ファイルを指定してください。");
            return "redirect:/";
        }

        // Azure FunctionsのfileUpload関数を呼び出すためのヘッダー情報を設定する
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        // Azure FunctionsのfileUpload関数を呼び出すための引数を設定する
        FileUploadParam fileUploadParam = new FileUploadParam();
        try {
            fileUploadParam.setFileName(uploadFile.getOriginalFilename());
            fileUploadParam.setFileData(ArrayUtils.toObject(
                IOUtils.toByteArray(uploadFile.getInputStream())));
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
        HttpEntity<FileUploadParam> entity 
            = new HttpEntity<>(fileUploadParam, headers);

        // Azure FunctionsのfileUpload関数を呼び出す
        ResponseEntity<FileUploadResult> response = restTemplate.exchange(
              demoAzureFuncBase + "fileUpload", HttpMethod.POST
            , entity, FileUploadResult.class);
        FileUploadResult fileUploadResult = response.getBody();

        // メイン画面へ遷移
        model.addAttribute("message", fileUploadResult.getMessage());
        return "redirect:/";
    }

    /**
     * ファイル名をUTF-8でエンコードする.
     * @param filePath ファイル名
     * @return エンコード後のファイル名
     */
    private String getFileNameEncoded(String fileName) {
        String fileNameAft = "";
        if (!StringUtils.isEmptyOrWhitespace(fileName)) {
            try {
                // ファイル名をUTF-8でエンコードして返却
                fileNameAft = URLEncoder.encode(fileName, "UTF-8");
            } catch (Exception e) {
                System.err.println(e);
                return "";
            }
        }
        return fileNameAft;
    }

}

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-rest-template-object/demoAzureApp



freelance hubを利用して10万件を超える案件情報からJava Spring案件を検索してみたfreelance hubは、レバテックフリーランスやフリエン(furien)を始めとした多くのフリーランスエージェントの案件をまとめて...

作成したサンプルプログラム(Azure Functions側)の内容

作成したサンプルプログラム(Azure Functions側)の構成は以下の通り。
サンプルプログラムの構成(AzureFunctions側)
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。

pom.xml(追加分)の内容は以下の通りで、ArrayUtilsクラスを含むcommons-lang3の設定を追加している。

<!-- ArrayUtilsクラスを含むcommons-lang3の設定 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
</dependency>

また、ハンドラークラスの内容はそれぞれ以下の通りで、@HttpTriggerアノテーションでParamクラスのHttpRequestMessageを受け取るようにしている。

package com.example;

import java.util.Optional;

import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import org.springframework.http.MediaType;

import com.example.model.FileUploadParam;
import com.example.model.FileUploadResult;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;

public class FileUploadHandler 
    extends FunctionInvoker<FileUploadParam, FileUploadResult> {

    /**
     * HTTP要求に応じて、DemoAzureFunctionクラスのfileUploadメソッドを呼び出し、
     * その戻り値をボディに設定したレスポンスを返す.
     * @param request リクエストオブジェクト
     * @param context コンテキストオブジェクト
     * @return レスポンスオブジェクト
     */
    @FunctionName("fileUpload")
    public HttpResponseMessage execute(
            @HttpTrigger(name = "request"
                , methods = HttpMethod.POST
                , authLevel = AuthorizationLevel.ANONYMOUS) 
              HttpRequestMessage<Optional<FileUploadParam>> request,
            ExecutionContext context) {

        // リクエストオブジェクトからFileUploadParamオブジェクトを取得する
        FileUploadParam fileUploadParam = request.getBody().get();

        // handleRequestメソッド内でDemoAzureFunctionクラスのfileUploadメソッドを呼び出し、
        // その戻り値をボディに設定したレスポンスを、JSON形式で返す
        return request.createResponseBuilder(HttpStatus.OK)
                .body(handleRequest(fileUploadParam, context))
                .header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                .build();
    }
}
package com.example;

import java.util.Optional;

import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import org.springframework.http.MediaType;

import com.example.model.GetFileListParam;
import com.example.model.GetFileListResult;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;

public class GetFileListHandler 
    extends FunctionInvoker<GetFileListParam, GetFileListResult> {

    /**
     * HTTP要求に応じて、DemoAzureFunctionクラスのfileUploadメソッドを呼び出し、
     * その戻り値をボディに設定したレスポンスを返す.
     * @param request リクエストオブジェクト
     * @param context コンテキストオブジェクト
     * @return レスポンスオブジェクト
     */
    @FunctionName("getFileList")
    public HttpResponseMessage execute(
            @HttpTrigger(name = "request"
                , methods = HttpMethod.POST
                , authLevel = AuthorizationLevel.ANONYMOUS) 
              HttpRequestMessage<Optional<GetFileListParam>> request,
            ExecutionContext context) {

        // リクエストオブジェクトからFileUploadParamオブジェクトを取得する
        GetFileListParam getFileListParam = request.getBody().get();
                
        // handleRequestメソッド内でDemoAzureFunctionクラスのfileUploadメソッドを呼び出し、
        // その戻り値をボディに設定したレスポンスを、JSON形式で返す
        return request.createResponseBuilder(HttpStatus.OK)
                .body(handleRequest(getFileListParam, context))
                .header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                .build();
    }
}

さらに、サービスクラスの内容はそれぞれ以下の通りで、ファイルデータがbyte配列からByte配列に変わったため、ファイルデータの設定/取り出しを、ArrayUtilsクラスのメソッドを用いるよう変更している。

package com.example.service;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.example.model.FileUploadParam;
import com.example.model.FileUploadResult;
import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.blob.BlobOutputStream;
import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;

@Service
public class FileUploadService {

    /** Azure Storageのアカウント名 */
    @Value("${azure.storage.accountName}")
    private String storageAccountName;

    /** Azure Storageへのアクセスキー */
    @Value("${azure.storage.accessKey}")
    private String storageAccessKey;
    
    /** Azure StorageのBlobコンテナー名 */
    @Value("${azure.storage.containerName}")
    private String storageContainerName;

    /**
     * ファイルアップロード処理を行うサービス.
     * @param fileUploadParam ファイルアップロード用Param
     * @return ファイルアップロードサービスクラスの呼出結果
     */
    public FileUploadResult fileUpload(FileUploadParam fileUploadParam) {        
        // ファイルアップロード処理
        try {
            // Blobストレージへの接続文字列
            String storageConnectionString = "DefaultEndpointsProtocol=https;" 
                    + "AccountName=" + storageAccountName + ";" 
                    + "AccountKey=" + storageAccessKey + ";";

            // ストレージアカウントオブジェクトを取得
            CloudStorageAccount storageAccount 
                = CloudStorageAccount.parse(storageConnectionString);

            // Blobクライアントオブジェクトを取得
            CloudBlobClient blobClient = storageAccount.createCloudBlobClient();

            // Blob内のコンテナーを取得
            CloudBlobContainer container 
                = blobClient.getContainerReference(storageContainerName);

            // Blob内のコンテナーにデータを書き込む
            CloudBlockBlob blob = container.getBlockBlobReference(
                fileUploadParam.getFileName());
            BlobOutputStream output = blob.openOutputStream();
            output.write(ArrayUtils.toPrimitive(fileUploadParam.getFileData()));
            output.close();

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

        FileUploadResult result = new FileUploadResult();
        result.setMessage("ファイルアップロードが完了しました。");
        return result;
    }
}
package com.example.service;

import java.io.InputStream;
import java.util.ArrayList;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.example.model.FileData;
import com.example.model.GetFileListParam;
import com.example.model.GetFileListResult;
import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.blob.CloudBlobClient;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;
import com.microsoft.azure.storage.blob.ListBlobItem;

@Service
public class GetFileListService {

    /** Azure Storageのアカウント名 */
    @Value("${azure.storage.accountName}")
    private String storageAccountName;

    /** Azure Storageへのアクセスキー */
    @Value("${azure.storage.accessKey}")
    private String storageAccessKey;

    /** Azure StorageのBlobコンテナー名 */
    @Value("${azure.storage.containerName}")
    private String storageContainerName;

    /**
     * ファイルリスト取得処理を行うサービス.
     * @param getFileListParam ファイル取得用Param
     * @return ファイルリスト取得サービスクラスの呼出結果
     */
    public GetFileListResult getFileList(GetFileListParam getFileListParam) {
        GetFileListResult result = new GetFileListResult();

        // ファイルアップロード処理
        try {
            // Blobストレージへの接続文字列
            String storageConnectionString = "DefaultEndpointsProtocol=https;" 
                    + "AccountName=" + storageAccountName + ";" 
                    + "AccountKey=" + storageAccessKey + ";";

            // ストレージアカウントオブジェクトを取得
            CloudStorageAccount storageAccount 
                = CloudStorageAccount.parse(storageConnectionString);

            // Blobクライアントオブジェクトを取得
            CloudBlobClient blobClient = storageAccount.createCloudBlobClient();

            // Blob内のコンテナーを取得
            CloudBlobContainer container 
                = blobClient.getContainerReference(storageContainerName);

            // Blob情報を取得し、戻り値に設定する
            ArrayList<FileData> fileDataList = new ArrayList<>();
            int index = 1;
            for (ListBlobItem blobItem : container.listBlobs()) {
                // ファイル名・ファイルパスを取得
                String filePath = blobItem.getUri().toString();
                String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);

                // Blobデータを読み込み
                CloudBlockBlob blob = container.getBlockBlobReference(fileName);
                InputStream input = blob.openInputStream();

                // ファイルデータを設定
                FileData fileData = new FileData();
                fileData.setId(index);
                fileData.setFileName(fileName);
                fileData.setFilePath(filePath);
                fileData.setFileData(ArrayUtils.toObject(IOUtils.toByteArray(input)));
                fileDataList.add(fileData);
                index++;
            }
            result.setFileDataList(fileDataList);

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }

        return result;
    }
}

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-rest-template-object/demoAzureFunc

サンプルプログラムの実行結果

サンプルプログラムの実行結果は、以下の各記事の「作成したサンプルプログラムの実行結果」と同じになる。

Azure Blob Storageのコンテナー内にファイルを格納するプログラムを作成してみた以前、JavaでSpring Bootフレームワークを利用してファイルをアップロードするプログラムを作成したことがあったが、ファイルのア...
Azure FunctionsでAzure Blob Storageのコンテナからファイルをダウンロードしてみた以前、Azure FunctionsでAzure Blob Storageのコンテナー内にファイルを格納する処理を実施してみたが、今回は...

要点まとめ

  • Azure App ServiceからAzure Functionsを呼び出す際に利用するRestTemplateを用いたAPI通信は、オブジェクトのままで通信を行うこともできる。その際、byte配列の項目があればByte配列に変更し、空のParamオブジェクトがあれば@JsonSerializeアノテーションを付与する必要がある。
  • byte配列⇔Byte配列の変換を行うには、Apache Commons Lang 3内のArrayUtilsクラスのメソッドを用いればよい。