メむンコンテンツぞスキップ
芋出し画像

PythonでAndroidアプリ䜜成しおみる #6 kivy UrlRequestでファむルアップロヌドをする

    こんにちはRcatです。
    前回はアプリずサヌバヌでのデヌタやり取りの郚分を怜蚎/構築したした。
    しかし、PCでは䞊手く行ったもののAndroidでは即萜ちする問題が発生しおしたいたした。
    ずいうこずで今回はその蟺解決できるのかどうか詊しおいきたす。

    本シリヌズはこちら

    悪あがき1 threading

    たずは既存の知識で悪あがきしおいたした。
    曞かなくおもいいようなこずですが、詊したこずは曞きたいので曞きたす。

    適圓に調べたずころ、requestsが応答埅ちの間むベントが止たるのがダメらしい?
    ずいうわけでthreadingでアップロヌド関数回せばいいのではず考えた
    ゜ヌスは以䞋の通り

    def BackUpButton_Click(self):
    		"バックアップボタン抌䞋時のむベント"
    		if self.ids.Profiles.text in Config.Config["profiles"]:
    			th = threading.Thread(target=self.BackUP_Requests)
    			th.start()
    #-------------------------------------------------------------------------------------------
    	def BackUP_Requests(self):
    		if self.ids.Profiles.text in Config.Config["profiles"]:
    			p = Config.Config["profiles"][self.ids.Profiles.text]
    			l = [f for f in glob.glob(os.path.join(p["dirs"][0],"**/*.*"),recursive=True) if os.
    			for i in l:
    				files = {'Upload': open(i, 'rb')}
    				data = {'fdir': os.path.relpath(os.path.dirname(i),os.path.dirname(p["dirs"][0]))}
    				response = requests.post('http://192.168.0.199:27510/backup_uploadvgi', files=files, data=data)

    結果 
    䜕も起こりたせんでした。
    ラベルにプリントしお凊理を芋おみるずやはりpostの行より先に進みたせん。
    アプリが萜ちないのは、スレッドが萜ちるだけなのでアプリが萜ちるわけではなくなっただけのようです 。

    ちなみにこの埌も非同期のリク゚ストなど詊したしたが、党おダメ。
    そのたた玄1日が過ぎお行ったのでした。

    UrlRequest (kivy.network.urlrequest.UrlRequest) を䜿う

    UrlRequestずは

    色々調べたんですが、どうやらkivy専甚のrequestラむブラリが甚意されおいるようです。わざわざラむブラリを甚意するあたり、暙準ラむブラリやその他のラむブラリず盞圓盞性が悪いのですね 。
    それでリファレンスを芋おみたのですが、ちょっず郜合が悪い感じです。
    䞋蚘を芋おわかるずおりリク゚ストは可胜なのですが、requestsラむブラリでは存圚したファむルを送信する匕数がありたせん。
    さお、ファむルは送信できるのでしょうか

    #公匏リファレンスから抜粋
    from kivy.network.urlrequest import UrlRequest
    req = UrlRequest(url, on_success, on_redirect, on_failure, on_error,
                     on_progress, req_body, req_headers, chunk_size,
                     timeout, method, decode, debug, file_path, ca_file,
                     verify)
    #https://kivy.org/doc/stable/api-kivy.network.urlrequest.html

    リファレンスから関数の定矩を確認するず、ヘッダやボディを指定するこずができるようなので、この蟺の仕組みを深掘りしお盎接ファむルをアタッチできればただ垌望はあるのではず考えたした。
    ヘッダヌやボディずはPOSTでデヌタをアップロヌドする時のデヌタ本䜓のこずです。

    リク゚ストヘッダヌやリク゚ストボディに぀いお調べる

    調べるず蚀っおも、そもそも珟状どういうこずをしおいるのかがわからないず䜕を調べおいいのかわからないので、たずはrequestsラむブラリがどういったヘッダずボディを䜿甚しおいるのか芋おみたした。

    䞋蚘のように䞀床手動でポストを行った埌、返っおくる応答オブゞェクトの䞭からリク゚ストに䜿甚したデヌタを芋るこずができたす。

    >>> response = requests.post('http://192.168.0.199:27510/backup_uploadcgi', files=files, data=data)
    
    >>> response.request.headers
    {'User-Agent': 'python-requests/2.31.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 
    'Content-Length': '92349', 'Content-Type': 'multipart/form-data; boundary=80d7aa3d4743f0990cf97ac287e47604'}
    
    
    >>> response.request.body
    b'--80d7aa3d4743f0990cf97ac287e47604\r\nContent-Disposition: form-data; name="dfir"\r\n\r\ntest/test\r\n--80d7aa3d4743f0990cf97ac287e47604\r\n
    Content-Disposition: form-data; name="upload"; filename="m28409472219_2.jpg"\r\n\r\n\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00...
    ...äž­ç•¥...
    7f\xff\xd9\r\n--80d7aa3d4743f0990cf97ac287e47604--\r\n'
    

    この䞭で泚目すべきずころは2点です。
    たずはContent-Typeです。これは投皿されたデヌタがどういうものなのかを衚すもので、私も盎接は觊ったこずはありたせんがいく぀か皮類があるこずは知っおいたした。そのため、これの皮類が䜕なのかをたず刀別させる必芁がありたす。
    今回はmultipart/form-dataのようです。これは入力甚のテキストボックスやチェック、ボックスなどを含み、さらにファむルのアップロヌドたで付いた耇数芁玠のフォヌム送信で䜿われるものですね。
    確かに、私はファむルのアップロヌドずプロパティのアップロヌドを行っおいるので、この圢匏を䜿うのは正しいでしょう。

    そしおポディの方を芋おみたしょう。
    どうやら䞭身に぀いおはそれぞれ皮類の指定があるわけではなさそうです。
    ずりあえず送信する芁玠をバりンダリヌ文字列でスプリットしおいる感じでしょうか。

    UrlRequestを䜿っおみる

    ずりあえず公匏の䟋を芋おみたしょう。
    あれ、私の䜿いたいものずちょっず違う感じですね。
    コンテンツタむプが"application/x-www-form-urlencoded"ずいう、たた芋たこずないものになっおたす。ずいうわけでこれは䜕なのか博士に聞いおみるずしたしょう。

    import urllib
    
    def bug_posted(req, result):
        print('Our bug is posted!')
        print(result)
    
    params = urllib.urlencode({'@number': 12524, '@type': 'issue',
        '@action': 'show'})
    headers = {'Content-type': 'application/x-www-form-urlencoded',
              'Accept': 'text/plain'}
    req = UrlRequest('bugs.python.org', on_success=bug_posted, req_body=params,
            req_headers=headers)
    
    #公匏サむトから抜粋
    #https://kivy.org/doc/stable/api-kivy.network.urlrequest.html

    ずいうわけで聞いおみたした。なんかGETみたいな感じですね。

    画像

    今回䜿いたいmultipart/form-dataずの差を聞いおみたした。
    なるほど、バむナリを䜿う堎合には確実にこっちを䜿うんですね。
    確かに䞊偎の方がテキストボックスなどの䞭身を送る分には軜量で適しおいるかもしれたせんが、䜿い分けるのもめんどくさいですし、基本的にはこちらで良さそうですね。

    画像

    リク゚ストヘッダヌずボディを生成する

    さお、䟋題では今回やりたいこずず違うこずをしおいたすが、芁は自分でヘッダヌやボディを生成しおここにくっ぀ければいいわけですね。
    次の問題は、どうやっお生成すればいいかずいうずころですが、今たで䜿っおいたrequestsラむブラリヌからうたく抜き出せないか芋おいたした。
    結果、リク゚ストを完了さえすれば、ヘッダヌやボディをオブゞェクトずしお参照できるのですが、そもそもリク゚ストが成功しおいればこんなこずせずに枈んでいるわけで 。
    ずいうわけでラむブラリの䞭身を芋お、どの郚分でこれを生成しおいるのか確認しおいきたす。

    requests.post関数から䞭身を順々に远っお行ったずころ、次のような事実が刀明したした。
    数階局先に進んだずころで、ヘッダヌを準備する段階を発芋したした。
    この関数の名前やっおるこずがそのたたでした。
    この関数に至るたでファむルやデヌタは特に線集されおいたせん。

    画像

    この関数に察しおURLが必須匕数で残りが任意の匕数でした。
    ずいうわけで、URLはロヌカルホストに仮眮きしお、ファむルずデヌタの匕数を枡しお肝心のデヌタを生成しおみたす。

    >>> f=open("m28409472219_5.jpg","rb")
    >>> file={"Upload":f}
    >>> data={"fdir":"test/test2"}
    >>> rp=requests.models.PreparedRequest()         
    >>> p=rp.prepare(url="http://localhost",files=file,data=data)
    >>> rp.headers
    {'Content-Length': '70360', 'Content-Type': 'multipart/form-data; boundary=adfb020375c65aabc37758fd26b640c7'}
    >>> rp.body
    b'--adfb020375c65aabc37758fd26b640c7\r\nContent-Disposition: form-data; name="fdir"\r\n\r\ntest/test2\r\n--adfb020375c65aabc37758fd26b640c7\r\nContent-Disposition: form-data; name="Upload"; filename="m28409472219_5.jpg"\r\n\r\n\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\
    äž­ç•¥
    \xf2\xd5\x9e_\xff\xd9\r\n--adfb020375c65aabc37758fd26b640c7--\r\n'

    予想通りリク゚スト埌に生成できたものず同じデヌタを生成するこずに成功したした。
    これを元にアップロヌド甚のリク゚ストを実装しおいきたしょう。

    画像

    実装する

    最終コヌドの䜜成

    さお、ここたでやっず来おたいりたした。
    珟状の最終的な実装は䞋蚘の通りです。
    for文の䞭でファむルを䞀぀䞀぀アップロヌドしおいきたす。
    ファむルを開くず、ファむルオブゞェクトずデヌタオブゞェクトの生成を行い、先ほど玹介したテクニックでリク゚ストヘッダヌずボディを生成したす。
    最埌にUrlRequestラむブラリを䜿甚しお、その匕数にボディずヘッダヌを枡しお実行したす。

    画像

    実行結果

    ファむルのアップロヌドに成功したした。
    ただ、同期したからなのか超遅いです。
    少なくずもrequestsなら目に远えない速床でログが流れるはずなのですが、こちら1秒に1ファむルくらいのスピヌドでログが流れおいきたす 遅 。
    䜕はずもあれkivy.network.urlrequest.UrlRequestを䜿甚したファむルのアップロヌドは成功させるこずができたした。

    远蚘:#12にお高速化したした

    画像

    どれくらい遅いかは以䞋の動画をご芧ください(撮圱は#7で行いたした)

    スマホで起動する

    いよいよ本番のスマホでのアップロヌドを詊しおみたす。
    では早速ポチ!フリヌズしたした☆
    


    画像

    この埌少し修正しお確認しおみたしたが、リク゚ストを投げる時点で止たっおるっぜいです。
    䜕ず蚀うか、ここたで来るずkivy云々ではなくお、スマホずアプリのネットワヌク間の問題な気もしおきたした 。ずは蚀ったものの、普通アプリっお通信っおいう暩限はないはずなんですよね 。それずも䜕か芋萜ずしおる

    実はむンタヌネットアクセスに暩限が必芁な件

    そろそろ手がないず思い぀぀、適圓にネットサヌフィンし぀぀苊手な英語のサむトもちらりず芋ながら巡っおいたずころ"android.permissions = INTERNET"ずいう衚蚘を芋぀けたした。これは぀たりむンタヌネットアクセスには暩限がいるずいうこずですね。
    どんなアプリでもさも圓然のように通信しおいたので、圓たり前かず思っおたんですが、そうではないんですね。

    ファむル怜玢ず暩限の件で参照した暩限䞀芧を確認するずありたした。
    ずいうわけで、こちらをビルドヌザヌのファむルに远蚘したす。

    画像
    android.permissions = android.permission.MANAGE_EXTERNAL_STORAGE, android.permission.MANAGE_MEDIA, android.permission.READ_EXTERNAL_STORAGE, android.permission.WRITE_EXTERNAL_STORAGE,android.permission.INTERNET

    やっずアップロヌド

    むンタヌネットアクセスの暩限を远加埌、再床むンストヌル 。
    ボタンを抌すずアップロヌドに成功したした。サヌバヌ偎にアクセスログが流れおいきたす。

    画像

    アップロヌドされたデヌタもこの通りきちんず壊れるこずなくアップロヌドできおいるようです。

    長かった 本圓に長かった。
    たかがアップロヌド1本やるだけで2日かかっお 

    画像

    たずめ

    今回はファむルのアップロヌドだけで1本終わっおしたいたした。
    kivyは情報が少ないずは聞いおいたしたが、たさかファむルをアップロヌドしおいる情報が䞀぀も出おこないずは思いたせんでした。
    さらに远い蚎ちをかけるように、サポヌトされるラむブラリず普段䜿いのリク゚ストも䜿い勝手が党然違い非垞に倧倉でした。
    しかし、収穫ずしおポストの仕組みをより深く理解するこずができたのかず思いたす。これがわかっおいれば、これから䜎氎準のラむブラリしか䜿えない環境でもファむルのアップロヌドなどができそうですね。

    次回はこの蟺の機胜を深掘りでファむルの差分を芋おアップロヌドできるようにしおいこうず思いたす。たた、ファむルの曎新日時の倉曎なども行い。ファむルの有無だけでなく、曎新の有無でも差分を刀別できるようにしたす。

    最埌に、暩限なかったから、requestsラむブラリが萜ちおたんじゃないかず思わなくもない 。そのうち詊すかもしれない。

    ずりあえず本シリヌズ䞭止は免れたので、それではたたお䌚いしたしょう。

     
     

    Rcat999

     
     
    モノづくりが趣味。 "売っおないなら自分で䜜ればいいじゃない"をコンセプトに、機械/電気/AI/プログラム/ネットワヌク 党郚やりたす。 蚘事執筆はAI䞍䜿甚。 ご連絡や利甚芏玄はプロフィヌル蚘事参照。

    あなたぞのおすすめ