Spring FrameworkのMultipartFileの仕組みについて調べてみた

f:id:ts0818:20220227010419j:plain

nazology.net

ヒトは生まれながらに歌う生き物のようです。

米国MIT(マサチューセッツ工科大学)の研究によれば、会話や楽器演奏では活性化しない、歌にだけ反応する脳回路が発見された、とのこと。

楽器には反応せず、歌にだけに反応する「歌回路」が脳内に存在していた - ナゾロジー

⇧ ついつい、hummingしてしまうことあるあるだよね~、何でだろうって謎が少しだけ解き明かされたということでしょうか。

今回は、Spring FrameworkのMultipartFileってものについて調べてみました。

レッツトライ~。

Spring FrameworkのMultipartFileとは?

Spring FrameworkAPIドキュメントを確認すると、

docs.spring.io

A representation of an uploaded file received in a multipart request.

The file contents are either stored in memory or temporarily on disk. In either case, the user is responsible for copying file contents to a session-level or persistent store as and if desired. The temporary storage will be cleared at the end of request processing.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html

⇧ 毎度のことながら、ボヤかしてくれてるけど、「multipart request」でアップロードファイルを受け取るらしいのですが、『The file contents are either stored in memory or temporarily on disk.』ということで、Spring Frameworkを利用したアプリケーションを動かしてるアプリケーションサーバーが動いているマシンの「メモリ」か「ディスク」に一時的に保存されるということらしい。

っていうか、「ディスク」に保存された時点で、永続化されるんじゃないの?ってツッコミどころ満載なんだが...

multipart requestって?

そもそもとして、「multipart request」が何なのか?

stackoverflow.com

As the official specification says, "one or more different sets of data are combined in a single body". So when photos and music are handled as multipart messages as mentioned in the question, probably there is some plain text metadata associated as well, thus making the request containing different types of data (binary, text), which implies the usage of multipart.

https://stackoverflow.com/questions/16958448/what-is-http-multipart-request

⇧ ということで、公式の仕様ってのを見てみると、「Content-Type」って出てきて、

media type (formerly known as MIME type) is a two-part identifier for file formats and format contents transmitted on the Internet. The Internet Assigned Numbers Authority (IANA) is the official authority for the standardization and publication of these classifications.

https://en.wikipedia.org/wiki/Media_type

Media types were originally defined in Request for Comments RFC 2045(MIME) Part One: Format of Internet Message Bodies (Nov 1996) in November 1996 as a part of MIME (Multipurpose Internet Mail Extensions) specification, for denoting type of email message content and attachments;hence the original name, MIME type. Media types are also used by other internet protocols such as HTTP and document file formats such as HTML, for similar purposes.

https://en.wikipedia.org/wiki/Media_type

⇧ まぁ、これが、

  • Media type
  • Mime type
  • Content-Type

って呼ばれ方が様々過ぎて、カオスを感じるのだけど...

インターネットで何かを送信する際の、

  • file formats
  • format contents

2つの取り決めが「Content-Type」ということらしいんだけど、「file formats」「format contents」が何を指すのかがハッキリしない...

Introduction
    (2)   A Content-Type header field, generalized from RFC 1049,
          which can be used to specify the media type and subtype
          of data in the body of a message and to fully specify
          the native representation (canonical form) of such
          data.

https://datatracker.ietf.org/doc/html/rfc2045

⇧ って部分の説明を見るに、まさかだけど、「type」「subtype」の2つのことを言ってるのかしら?

⇧ なんか、そんな気がしてならないけど...

ただ、「Content-Type header field」があるんなら、当然、「Content-Type body field」とかあるように考えてしまうんだが...

まぁ、脱線してしまいましたが、「multipart request」っていうのは、「Content-Type」の「type」が、「multipart」の場合の話ってことらしい。

「multipart」だと、一度のリクエストのBodyに異なるタイプのデータ型を複数持たせることが可能ということのようです。

www.iana.org

⇧ Webシステムとかだと、「multipart/form-data」をよく見るような気がしますかね。

Spring FrameworkのMultipartFileの仕組みについて調べてみた

で、Spring Frameworkの場合だと、

www.kimullaa.com

MultipartFileとは、ファイルのアップロード機能をアプリケーションコード内で透過的に扱うためのクラスです。アップロード機能の実装は、Servlet 3.0のファイルアップロードか、Apache Commons Fileuploadから選ぶことができます。

Spring MultipartFile アップロードが完了しないとControllerのメソッドは呼ばれないぞ! - SIerだけど技術やりたいブログ

⇧ 上記サイト様によりますと、実装方法については、2つ用意されてるらしく、実装については仕様を満たしてるはずだと思うので、クライアントからのリクエストデータをMultipartFileで受け取るということは、サーバー側では「メモリ」または「ディスク」に一時的にファイルが保存されるということですと。

qiita.com

spring.servlet.multipart.location

アップロードしたファイルをサーバ上のディスクに中間ファイルとして書き込むときの場所(ディレクトリ)を指定します。デフォルトは未指定で、アプリケーションサーバー既定の一時ファイル用のディレクトリが使用されます。

Spring MVC ファイルのアップロード - Qiita

⇧ 上記サイト様によりますと、アプリケーションサーバー既定の一時ファイル用のディレクトリに一時的に保存しておくというような設定が、Spring Frameworkを使ってる場合は、application.propertiesとかで設定できる模様。

2022年2月28日(月)追記:↓ ここから

ただ、

docs.spring.io

⇧ application.propertiesを確認してみるも、デフォルト値の情報が無いんですけど...

docs.spring.io

Properties to be used in configuring a MultipartConfigElement.

  • location specifies the directory where uploaded files will be stored. When not specified, a temporary directory will be used.
  • max-file-size specifies the maximum size permitted for uploaded files. The default is 1MB
  • max-request-size specifies the maximum size allowed for multipart/form-data requests. The default is 10MB.
  • file-size-threshold specifies the size threshold after which files will be written to disk. The default is 0.

https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/web/servlet/MultipartProperties.html

⇧ だ~か~ら~、「a temporary directory」ってのがどこなのかハッキリさせてくれないのは何故に?

ちなみに、MultipartProperties.javaを見てみたけど、

github.com

APIドキュメントと情報が変わらん...

ServerProperties.javaで、Tomcatのpropertiesの中でも、

github.com

	/**
	 * Tomcat properties.
	 */
	public static class Tomcat {

		/**
		 * Access log configuration.
		 */
		private final Accesslog accesslog = new Accesslog();

		/**
		 * Thread related configuration.
		 */
		private final Threads threads = new Threads();

		/**
		 * Tomcat base directory. If not specified, a temporary directory is used.
		 */
		private File basedir;
		
		...省略

⇧『a temporary directory is used.』って出てくるんだけども、頑なに『temporary directory』がどこのディレクトリのことを言ってるのかは明示してくれないというね...

ちなみに、Javaの標準で用意されてる「java.io.tmpdir」っていうシステムプロパティというものがあるらしいのですが、

propg.ee-mall.info

stackoverflow.com

アプリケーションサーバーとしてTomcatが起動してる場合で、且つ、Tomcat側で一時ディレクトリに関する設定とかして無い場合、「java.io.tmpdir」の値としてはTomcatの既定の一時ディレクトリのパスが取得されるらしい。

で、Spring Boot はデフォルトだとアプリケーションサーバーとしてはTomcatを利用しているので、application.propertiesの「spring.servlet.multipart.location」を設定する、または、Tomcatの設定ファイルで明示的に一時ディレクトリの設定するなどをしていない限り、Tomcatの既定の一時ディレクトリのパスが参照されるんだと。

それを、Spring Boot ないし、Spring Frameworkのドキュメントで分かるようにしておいていただけると非常に助かるんですけどね...

プロパティが参照される仕組みなんかも、

blog.andresteingress.com

⇧ 上記サイト様が頑張ってまとめてくださってますが、公式がドキュメントに頑張ってまとめるべきな気がするんだけどな...

結局「Spring Boot」が言う「temporary directory」が何なのかハッキリしないですし、推測するしかないというモヤモヤ感と芳しい情報が見当たらず徒労感が半端ない...

2022年2月28日(月)追記:↑ ここまで

2022年3月5日(土)追記:↓ ここから

Spring Bootのドキュメントで、

spring.pleiades.io

Spring Boot は、サーブレットjavax.servlet.http.Part API を採用して、ファイルのアップロードをサポートします。

https://spring.pleiades.io/spring-boot/docs/current/reference/html/howto.html#howto.spring-mvc.multipart-file-uploads

⇧ となっていて、「javax.servlet.http.Part」っていうAPIを使ってるらしいということで、

docs.oracle.com

This class represents a part or form item that was received within a multipart/form-data POST request. 

https://docs.oracle.com/javaee/7/api/javax/servlet/http/Part.html 

⇧ 確認してみたけど、頑なにtemporary directoryの場所についての説明はないというね...門外不出の秘伝みたいな感じで口承するかのような雰囲気を感じてしまうんですが...

もうちょっと気軽に情報を公開してくれると嬉しいんですが...

で「javax.servlet.http.Part」ってAPIは、

docs.oracle.com

What Is a Servlet?

servlet is a Java programming language class that is used to extend the capabilities of servers that host applications accessed by means of a request-response programming model. Although servlets can respond to any type of request, they are commonly used to extend the applications hosted by web servers. For such applications, Java Servlet technology defines HTTP-specific servlet classes.

https://docs.oracle.com/javaee/5/tutorial/doc/bnafe.html

The javax.servlet and javax.servlet.http packages provide interfaces and classes for writing servlets. All servlets must implement the Servlet interface, which defines life-cycle methods. When implementing a generic service, you can use or extend the GenericServlet class provided with the Java Servlet API. The HttpServlet class provides methods, such as doGet and doPost, for handling HTTP-specific services.

https://docs.oracle.com/javaee/5/tutorial/doc/bnafe.html

⇧「Servlet」を構成するパッケージの1つということですと。

で、ややこしいのが、

stackoverflow.com

Pathname to a scratch directory to be provided by this Context for temporary read-write use by servlets within the associated web application. This directory will be made visible to servlets in the web application by a servlet context attribute (of type java.io.File) named javax.servlet.context.tempdir as described in the Servlet Specification. If not specified, a suitable directory underneath $CATALINA_HOME/work will be provided.

http://tomcat.apache.org/tomcat-5.5-doc/config/context.html (search for workdir on the above page)

https://stackoverflow.com/questions/4750351/how-do-i-set-javax-servlet-context-tempdir-in-tomcat

⇧「javax.servlet.context.tempdir」ってのがtemporary directoryかと思いきや、

stackoverflow.com

  • work stores compiled JSPs and other assets.
    => You only need to "clean" the webapp directories under it on the rare occasions when an update to a WebApp is not picked up.

  • temp is used to store files created using the Java File API for creating temporary files.
    => You can "clean" it every time you restart Tomcat. If it is getting too full then one of your WebApps may have some form of leak where its not releasing temp files when its done with them (although it could just be under higher load too).

 

https://stackoverflow.com/questions/8853387/clarifications-on-tomcats-temp-and-work-directories 

⇧ tempってdirectoryがtemporary directoryってことになるらしい。

Apache Tomcatの「Context」の「Attribute」の1つである「createUploadTargets」っていう項目の説明を確認してみると、

tomcat.apache.org

Set to true if Tomcat should attempt to create the temporary upload location specified in the MultipartConfig for a Servlet if the location does not already exist. If not specified, the default value of false will be used. 

https://tomcat.apache.org/tomcat-9.0-doc/config/context.html 

⇧ なんか、Apache Tomcatのデフォルトの設定だと、Spring Boot のapplication.propertiesの「spring.servlet.multipart.location」で一時ディレクトリのパスを指定したとしても作成されないってことのように見えるんだけど、Spring Boot のドキュメントで言及無いんだが...

ちなみに、Apache Tomcatのcontext.xmlを確認したところ、

github.com

⇧「createUploadTargets」のdefault値はfalseになってるから、Spring Boot のapplication.propertiesの「spring.servlet.multipart.location」で一時ディレクトリのパスを指定しても、Apache Tomcatはtemporary directoryを作成してくれないってことね...

ただ、

serverfault.com

The java.io.tmpdir in Tomcat is set to $CATALINA_BASE/temp. You can change it by setting the $CATALINA_TMPDIR environment variable before running startup.sh for Tomcat.

https://serverfault.com/questions/13523/configure-tomcat-to-use-a-different-temp-directory-for-file-uploads

⇧ Spring Boot のapplication.propertiesの「spring.servlet.multipart.location」の値に「java.io.tmpdir」を設定してあげれば、Apache Tomcat の既定のtemporary directoryが利用されるらしいので、temporary directory作成する必要がなくなるから、Apache Tomcat の「context.xml」の「createUploadTargets」の値はfalseのままで良いってことかと思ったけど、一時ディレクトリにサブディレクトリとか作るんなら、「createUploadTargets」の値はtrueにしなきゃダメってことか...

これ、アプリケーションサーバーがApache Tomcat以外だった場合、同じ様にアプリケーションサーバー側で設定の修正が必要ってことなんかな?

分かり辛過ぎるんだが...

2022年3月5日(土)追記:↑ ここまで

まぁ、なので、Spring Framework使ってる場合は、アプリケーションサーバーとしてはApache Tomcatを使ってることが多いとは思うのですが、アプリケーションサーバーがインストールされてるマシンの「メモリ」または「ディスク」に一時的にアップロードされたファイルが保存されるのだと。

atmarkit.itmedia.co.jp

⇧ 上記サイト様のイメージ図が流れが掴みやすいかと。

まぁ、何が言いたいかと言うと、例えば、サーバー側にアップロードされてきたMultipartFileでリサイズとかしたものも保存したいってなった場合は、アップロードされたMultipartFileから一時ファイルを作っておく必要があるってことですな。

MultipartFileも一時ファイルだと思うので、一時ファイルから一時ファイルを複製する感じになるんかな。

一時ファイルの作成は、Javaの標準APIのみで実現可能らしいですが、

qiita.com

Javaのバージョンによって利用できるAPIが異なるので注意ですかね。

あとは、パスの区切り文字で、

aoking.hatenablog.jp

Linux のパス形式は /path/to/file であるが、Windows は C:\path\to\resource である。つまり、セパレーターが / と \ で異なる。厳密にはこれらの値はハードコーディングせず、File.separator を用いるべきだが、実は Windows はセパレーターに / を用いることができる。

クロスプラットフォームな Java コードの書き方 - にょきにょきブログ

Windowsがやっぱり厄介ということみたいですね、APIとかでプラットフォーム毎のセパレーターを取得したりしてるソースコードだと、「\(バックスラッシュ)」と「/(スラッシュ)」が混在しかねることが起こり得るという...

すべてFile.separatorを利用していれば良いのかもしれませんが、「/(スラッシュ)」をべた書き(ハードコーディング)してる場合、Windowsのセパレーターが「\(バックスラッシュ)」であることによる弊害を被るリスクが出てきますと。

で、話を元に戻すと、MultipartFileでリサイズ処理などを実施するケースにおいて、MultipartFileでリサイズ処理を行った後に、再びMultipartFileにするのはほぼできないらしい。

stackoverflow.com

⇧ 上記を確認したところ、

spring.pleiades.io

⇧ Mockを使うというトリッキーな方法でしかMultipartFileってのは実現できないらしいですと...

2022年3月1日(火)追記:↓ ここから

「Gradle」や「Maven」といったビルドツールを利用している場合は、「Apache Commons FileUpload」ってライブラリを「依存関係」に追加すれば、Mockを使わずにMultipartFileを作ることもできる模様。

Spring Framework単独だといろいろ実現するのは難しいみたいね...

2022年3月1日(火)追記:↑ ここまで

何が残念って、Spring FrameworkではMultipartFileをリサイズするってことを想定してないのか分からないのですが、ドキュメントではリサイズ処理とかについて触れられてないというね...

ファイルのリサイズ処理って業務上確実に必要となるケースがあると思うんだけど、フレームワークってどうも実務的な使い方を想定してないんかな?

なんか、中途半端な機能が多いというか、痒い所に手が届かないというか、かといってドキュメントの情報量も微妙だし...

arimodoki.mydns.jp

⇧ 上記サイト様によると、MultipartFileをリサイズする場合、Fileにするところまでしか復元できないっぽいですね。

他のネット情報を見てみても、MultipartFileをリサイズした後に、MultipartFileの形に復元するのは無理らしい。

元のデータ型に復元できないのは気持ち悪いですが、MultipartFileに戻せなくても情報が足りてるのならどんなデータの形になろうが問題はないらしい。

ただ、MultipartFile自体がどんな情報を持ってるのかがいまいちよく分からんのが一番の問題の気がするんだが...

www3.ntu.edu.sg

⇧ 上記サイト様によりますと、Java側でデータ受け取る場合は、最終的に

  • InputStream
  • byte

を渡してあげれば元のデータが何であったとしても何とかしてくれるってことなんですかね?

イメージ的には、

f:id:ts0818:20220227100558p:plain

⇧ みたいな感じになるんかな。外部システムが採用してるAPIとかの仕様によって、外部システムに送る必要な情報(加工の仕方)は変わってくるとは思うけど。

今時のシステムだと、クラウドを利用することが多いと思うけども、サーバー(アプリケーションサーバー)と外部システム(ストレージサービス)を分けるというのは、オンプレミス環境と同じく、クラウドでも取り入れられてるかと。

クラウドで実現するとなると、

クラウド サーバー ストレージ
GCP
Google Cloud Plattform)
Google Compute Cloud Storage
Microsoft Azure Azure Compute Azure Storage
AWS
Amazon Web Service)
Amazon Compute AWS Storage

⇧ のようなサービスを利用していく感じになるんかな。上記のサービスも種類が多くて、どれ使ったら良いか迷いますよね...。

ちなみに、Microsoft Azureだと、

azure.microsoft.com

⇧「Compute」サービスだけで、20個近くあります...

毎度モヤモヤ感が半端ない...

今回はこのへんで。