質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

Q&A

解決済

1回答

1469閲覧

apiの戻り値がMULTIPART_FORM_DATE_VALUEに指定したHttpEntityの受け取り方

kou-kiri

総合スコア5

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

0グッド

0クリップ

投稿2022/05/13 11:55

編集2022/05/16 10:16

やりたいこと

マルチパートファイル型の戻り値のを返すAPIを呼んで、返ってきたマルチパートファイル型のファイルをローカルに保存する。

わからないこと

APIのレスポンスボディとしてバインドするクラスに何を指定すればいいかわからない

実装

呼び出し側

public void callApi() { RestTemplate rest = new RestTemplate(); RequestEntity req = new RequestEntity(HttpMethod.put, new URI("xxx")) // 問題箇所。 バインドするクラスをHttpEntityじゃなくStringにすると、APIは起動しString型で結果も取れる。 ResponseEntity<HttpEntity> res = rest.exchange(req, HttpEntity.class ); //取得したファイルの保存処理 }

エラー

org.springframework.web.HttpMediaTypeNotAcceptableExcption: Could not find acceptable representation
HttpclientErrorException
status 406

やったこと

バインドするクラスを自作のクラスにする。
中にはHttpEntityのフィールドのみ(getterとsetterも)

環境・言語

Java
spring boot
eclipse

追記

API単独で叩けば成功しますので、API側の問題ではないと思っています。
バインドするクラスをStringにすると呼び出しメソッドから呼び出しても成功するため、バインドするクラスの指定が間違っているのだと思っていますが、いかがでしょうか?

マルチパート型のリクエストの方法はネットにたくさん落ちていますが、
返却されたマルチパート型を使用する方法について、なかなか見つけられなかったので、質問させて頂きました。ご教授いただけると幸いです。よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dodox86

2022/05/13 23:02

エラーメッセージを見るとHTTPステータス406でNot Acceptable相当ですが、そもそもAPIのHTTPリクエストがサーバーに受け付けられていないのでは?従ってHTTPレスポンス中にマルチパートのファイル内容も含まれていない可能性がありますが、その辺りは大丈夫でしょうか。 Spring Bootは分からないので上記、コメントのみです。
kou-kiri

2022/05/14 01:17

バインドするクラスが間違っているため、サーバーに受付けられていないと考えております。 API単独で叩いて、成功しているため、メソッド内で呼び出す方法が間違っているのかなと・・・
guest

回答1

0

ベストアンサー

私は Spring Boot に詳しいわけではないため、色々試してみた上での結果となりますので、信憑性もその程度とお考え下さい。

ステータスコードが 406 となるのは、リクエストヘッダの Accept 値の不整合によるものの様です。RequestEntity に、ヘッダを指定できるコンストラクタがあるので、これで設定すると良いです。"Accept" をキー、MediaType.MULTIPART_FORM_DATA_VALUE を値とします。

これで 200 が返ってくるようにはなりますが、残念ながら、ローカルでのオブジェクトへのバインドには失敗します。

バインドは、RestTemplate の getMessageConverters() で取得できる、HttpMessageConverter<T> が行います。このリストのうち、バインドクラスと MediaType の双方の一致するものが基本的には選択されて、バインド処理を行う仕組みのようです。

デフォルトで登録されているもののうち、multipart/form-data を扱えるものは、
AllEncompassingFormHttpMessageConverter だけの様ですが、わざわざスーパークラスのドキュメントにも書いてある通り、なぜか multipart 系の読み取り(バインド)だけはできません。逆はできるのに。

ですので、適当な Converter を見繕い、getMessageConverters().add() する必要があります。既存のものに使えるものがあるのかどうかは分かりませんでしたが、ちなみに、自作するのであれば、まあまあ大変です。

ところで、どうしても multipart/form-data に拘らなければならないのでしょうか。デフォルトの application/json でやり取りすれば、かなり実装は楽そうなのですが・・・

折角なので、記念品(?)を残しておいてみます。

Java

1//ApiTestController.java 2 3package com.example.demo.controllers; 4 5import java.util.LinkedHashMap; 6import java.util.Map; 7 8import org.springframework.http.HttpEntity; 9import org.springframework.http.MediaType; 10import org.springframework.stereotype.Controller; 11import org.springframework.util.LinkedMultiValueMap; 12import org.springframework.util.MultiValueMap; 13import org.springframework.web.bind.annotation.RequestMapping; 14import org.springframework.web.bind.annotation.RequestMethod; 15 16 17@Controller 18public class ApiTestController { 19 20 private static final String[] contents = { 21 "あああああ", "いいいいい", "ううううう" 22 }; 23 24 /** 25 * multipart/form-data な API 26 */ 27 @RequestMapping(path = "/api1", method = RequestMethod.GET, produces = MediaType.MULTIPART_FORM_DATA_VALUE) 28 public HttpEntity<MultiValueMap<String, byte[]>> api1() { 29 30 MultiValueMap<String, byte[]> valueMap = new LinkedMultiValueMap<>(); 31 32 byte[][] files = new byte[contents.length][]; 33 for (int i = 0; i < contents.length; i ++) { 34 files[i] = contents[i].getBytes(); 35 } 36 37 // valueMapに値を詰め込む。 38 for (int i = 0; i < contents.length; i ++) { 39 valueMap.add("file" + i + ".txt", files[i]); 40 } 41 42 return new HttpEntity<>(valueMap); 43 } 44 45 /** 46 * application/json な API 47 */ 48 @RequestMapping(path = "/api2", method = RequestMethod.GET) 49 public HttpEntity<Map<String, String>> api2() { 50 51 Map<String, String> valueMap = new LinkedHashMap<>(); 52 53 // valueMapに値を詰め込む。 54 for (int i = 0; i < contents.length; i ++) { 55 valueMap.put("file" + i + ".txt", contents[i]); 56 } 57 58 return new HttpEntity<>(valueMap); 59 } 60}

Java

1//Main.java 2 3package com.example.demo; 4 5import java.io.IOException; 6import java.io.PrintWriter; 7import java.io.StringWriter; 8import java.net.HttpURLConnection; 9import java.net.URI; 10import java.net.URL; 11import java.nio.charset.Charset; 12import java.util.Arrays; 13import java.util.LinkedHashMap; 14import java.util.List; 15import java.util.Map; 16import java.util.Scanner; 17import java.util.function.Consumer; 18 19import org.springframework.http.HttpHeaders; 20import org.springframework.http.HttpInputMessage; 21import org.springframework.http.HttpMethod; 22import org.springframework.http.HttpOutputMessage; 23import org.springframework.http.MediaType; 24import org.springframework.http.RequestEntity; 25import org.springframework.http.ResponseEntity; 26import org.springframework.http.converter.HttpMessageConverter; 27import org.springframework.http.converter.HttpMessageNotReadableException; 28import org.springframework.http.converter.HttpMessageNotWritableException; 29import org.springframework.util.LinkedMultiValueMap; 30import org.springframework.util.MultiValueMap; 31import org.springframework.web.client.RestTemplate; 32 33 34public class Main { 35 36 @FunctionalInterface 37 private static interface Handler<T> extends Consumer<ResponseEntity<T>> { 38 } 39 40 @SuppressWarnings("unchecked") 41 public static void main(String[] args) throws Exception { 42 43 final String url = "http://localhost:8080"; 44 45 // multipart/form-data な API をコールする 46 47 RestTemplate rest1 = new RestTemplate(); 48 rest1.getMessageConverters().add(new MultipartMessageConverter()); 49 String api1 = url + "/api1"; 50 String accept1 = MediaType.MULTIPART_FORM_DATA_VALUE; 51 Class<?> class1 = Map.class; 52 53 System.out.println("## multipart/form-data ##"); 54// callApi(new URL(api1), accept1); 55 callApi(new URI(api1), accept1, rest1, (Class<Map<String, String>>)class1, Main::handler1); 56 System.out.println(); 57 58 // application/json な API をコールする 59 60 RestTemplate rest2 = new RestTemplate(); 61 String api2 = url + "/api2"; 62 String accept2 = MediaType.APPLICATION_JSON_VALUE; 63 Class<?> class2 = Map.class; 64 65 System.out.println("## application/json ##"); 66// callApi(new URL(api2), accept2); 67 callApi(new URI(api2), accept2, rest2, (Class<Map<String, String>>)class2, Main::handler2); 68 System.out.println(); 69 } 70 71 /** 72 * HttpURLConnection を使って API をコールする。 73 */ 74 public static void callApi(URL url, String accept) throws IOException { 75 76 HttpURLConnection conn = (HttpURLConnection)url.openConnection(); 77 conn.setRequestMethod("GET"); 78 conn.addRequestProperty("Accept", accept); 79 80 System.out.println("## Content-Type ##"); 81 System.out.println(conn.getHeaderField("Content-Type")); 82 83 System.out.println("## Messge Body ##"); 84 conn.getInputStream().transferTo(System.out); 85 } 86 87 /** 88 * RestTemplate を使って API をコールする。 89 */ 90 public static <T> void callApi(URI uri, String accept, RestTemplate rest, Class<T> clazz, Handler<T> handler) { 91 92 MultiValueMap<String, String>headers = new LinkedMultiValueMap<>(); 93 headers.add("Accept", accept); 94 RequestEntity<?> req = new RequestEntity<>(headers, HttpMethod.GET, uri); 95 96 ResponseEntity<T> res = rest.exchange(req, clazz); 97 handler.accept(res); 98 } 99 100 private static void handler1(ResponseEntity<Map<String, String>> res) { 101 102 System.out.println("## Content-Type ##"); 103 System.out.println(res.getHeaders().getContentType()); 104 105 Map<String, String> valueMap = res.getBody(); 106 107 for (Map.Entry<String, String> entry : valueMap.entrySet()) { 108 System.out.println("## " + entry.getKey() + " ##"); 109 System.out.println(entry.getValue()); 110 } 111 } 112 113 private static void handler2(ResponseEntity<Map<String, String>> res) { 114 115 System.out.println("## Content-Type ##"); 116 System.out.println(res.getHeaders().getContentType()); 117 118 Map<String, String> valueMap = res.getBody(); 119 120 for (Map.Entry<String, String> entry : valueMap.entrySet()) { 121 System.out.println("## " + entry.getKey() + " ##"); 122 System.out.println(entry.getValue()); 123 } 124 } 125} 126 127 128class MultipartMessageConverter implements HttpMessageConverter<Map<String, String>> { 129 130 private static final List<MediaType> supportedMediaTypes = Arrays.asList( 131 MediaType.MULTIPART_FORM_DATA 132 ); 133 134 @Override 135 public List<MediaType> getSupportedMediaTypes() { 136 return supportedMediaTypes; 137 } 138 139 @Override 140 public boolean canRead(Class<?> clazz, MediaType mediaType) { 141 142 if (!clazz.isAssignableFrom(LinkedHashMap.class)) { 143 return false; 144 } 145 146 if (mediaType == null) { 147 return false; 148 } 149 150 return supportedMediaTypes.get(0).includes(mediaType); 151 } 152 153 @Override 154 public Map<String, String> read(Class<? extends Map<String, String>> clazz, 155 HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { 156 157 MediaType contentType = inputMessage.getHeaders().getContentType(); 158 159 if (!canRead(clazz, contentType)) { 160 throw new HttpMessageNotReadableException("cannot read.", inputMessage); 161 } 162 163 Charset charset = contentType.getCharset(); 164 if (charset == null) { 165 charset = Charset.defaultCharset(); 166 } 167 168 String boundary = contentType.getParameter("boundary"); 169 170 Map<String, String> valueMap = new LinkedHashMap<>(); 171 try (Scanner sc = new Scanner(inputMessage.getBody(), charset)) { 172 173 sc.useDelimiter("--" + boundary + "(--)?\\r\\n"); 174 175 while (sc.hasNext()) { 176 readPart(sc.next(), valueMap); 177 } 178 } 179 180 return valueMap; 181 } 182 183 private void readPart(String part, Map<String, String> valueMap) throws IOException { 184 185 try (Scanner sc = new Scanner(part)) { 186 187 HttpHeaders headers = new HttpHeaders(); 188 while (sc.hasNextLine()) { 189 String line = sc.nextLine(); 190 if (line.isEmpty()) { 191 break; 192 } 193 String[] pair = line.split(": "); 194 headers.add(pair[0], pair[1]); 195 } 196 197 String name = headers.getContentDisposition().getName(); 198 199 StringWriter body = new StringWriter(); 200 PrintWriter w = new PrintWriter(body); 201 while (sc.hasNextLine()) { 202 w.print(sc.nextLine()); 203 if (sc.hasNextLine()) { 204 w.println(); 205 } 206 } 207 w.flush(); 208 209 valueMap.put(name, body.toString()); 210 } 211 } 212 213 @Override 214 public boolean canWrite(Class<?> clazz, MediaType mediaType) { 215 return false; 216 } 217 218 @Override 219 public void write(Map<String, String> t, MediaType contentType, HttpOutputMessage outputMessage) 220 throws HttpMessageNotWritableException { 221 throw new HttpMessageNotWritableException("not implemented."); 222 } 223}

投稿2022/05/16 01:55

momodx

総合スコア185

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問