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

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

ただいまの
回答率

89.10%

証明書付きのサーバに画像を送信できない場合がある

受付中

回答 0

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 236

front_sea

score 4

お初にお目にかかります。
会社にてアプリを作成している者で、何かあればここで検索しており非常に助かっております。
この度はどうしても解決できない問題があり、皆様のお力を借りたく質問を投稿いたしました。
何卒、お力添えいただきたく存じます。

前提・実現したいこと

実現したいこと:
SSL証明書を使用しているAzureサーバに、スマホ(Android)で撮影した写真をアップロードする

発生している問題・エラーメッセージ

問題:
撮影した写真の容量が大きい場合、送信に失敗することがある

容量は100kb(Mbではないです)を超えると必ず失敗します。
ただし、証明書を使用しない環境には問題なく送信ができております。

失敗時のエラー内容:
・ W/System.err: javax.net.ssl.SSLException: Write error
・ W/System.err: javax.net.ssl.SSLException: Read error
・connect pear
・タイムアウト

該当のソースコード

全部で3種類
・SendPic.java(写真撮影)
・AsyncHttps.java(送信用非同期処理)
・pic_upload.php(画像復元用)

流れ:
SendPicで撮影、byte配列に変換しAsyncHttpsに渡す(元のデータは残さない)
→AsyncHttpsでAzure web apps(SSL適用)上のphpファイルに送信
→phpファイルにて画像を復元

public class SendPic extends AppCompatActivity
{
    static final int REQUEST_CAPTURE_IMAGE = 100;

    File mediaFile;
    private Uri imageUri;


    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageButton btn_camera = findViewById(R.id.btn_camera);


        //ボタンを押されたこと検知するリスナーを設置
        btn_camera.setOnClickListener(BtnClickListener);


    }

    public View.OnClickListener BtnClickListener = new View.OnClickListener()
    {
        public void onClick(View v)
        {

            switch (v.getId())
            {
                default:
                    return;

                //承認された時用カメラボタン
                case R.id.btn_camera:
                    Intent intent_c = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                    File mediaStorageDir = new File
                            (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"MyApp");
                    //カメラ画像を保存するディレクトリ
                    if(!mediaStorageDir.exists())
                    {
                        if(!mediaStorageDir.mkdirs())
                        {break;}
                    }

                    //現在日時を取得
                    String timeStamp = new SimpleDateFormat(
                            "yyyy_MM_dd_HH_mm_ss").format(new Date());

                    //取得した現在日時をファイル名に
                    mediaFile = new File(mediaStorageDir.getPath() + File.separator
                            + timeStamp +".jpg");

                    imageUri = Uri.fromFile(mediaFile);

                    Uri uri = FileProvider.getUriForFile(
                            SendPic.this
                            ,getApplicationContext().getPackageName() + ".provider"
                            , mediaFile);
                    intent_c.putExtra(MediaStore.EXTRA_OUTPUT, uri);

                    startActivityForResult(intent_c,REQUEST_CAPTURE_IMAGE);
                    break;
            };
        };
    };

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent resultData)
    {
        //写真撮った時
        if(REQUEST_CAPTURE_IMAGE == requestCode && resultCode == Activity.RESULT_OK)
        {
            try
            {
                Bitmap bitmap= getBitmapFromUri(imageUri);
                if(bitmap == null)
                {return;}

                iv1.setScaleType(ImageView.ScaleType.MATRIX);
                iv1.setImageBitmap(bitmap);
                //画像のサイズを今の1/3に変更し
                final Bitmap changeScale = Bitmap.createScaledBitmap(bitmap,bitmap.getWidth()/3,bitmap.getHeight()/3,true);

                if(changeScale == null)
                {return;}

                //画像は保存しなくていいので削除
                mediaFile.delete();

                byte[] bytes;
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                //成功率と画質の兼ね合いで30とする
                changeScale.compress(Bitmap.CompressFormat.JPEG, 30, byteArrayOutputStream);
                bytes = byteArrayOutputStream.toByteArray();
                byteArrayOutputStream.close();

                //送信
                AsyncHttps.url_id = 0;
                new AsyncHttps(SendPic.this).execute(bytes);

            }
            catch (IOException e)
            {
                e.printStackTrace();
                Log.d("失敗:",String.valueOf(e));
            }
        }

    }
    //Uriから画像(Bitmap形式)を取得する
    private Bitmap getBitmapFromUri(Uri uri) throws IOException
    {
        ParcelFileDescriptor parcelFileDescriptor =
                getContentResolver().openFileDescriptor(uri,"r");
        FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
        Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
        parcelFileDescriptor.close();
        return image;
    }
}
public class AsyncHttps extends AsyncTask<byte[], Void, String>
{
    private Context mContext;
    public AsyncHttps(Context context)
    {mContext = context;}

    public static String res=null;
    public static  StringBuilder stblX = new StringBuilder();

    // 非同期処理
    @Override
    protected String doInBackground(byte[]... params)
    {
        String urlSt = "https://example/pic_upload.php"; //組織の名前が入っているので便宜上のURLです

        HttpURLConnection con = null;
        final StringBuilder stbl = new StringBuilder();
        try
        {this.registerClientCert(mContext );}
        catch ( Exception e )
        {e.printStackTrace();}

        try
        {
            // URL設定
            URL url = new URL(urlSt);
            // HttpURLConnection
            con = (HttpsURLConnection) url.openConnection();
            // no Redirects
            con.setInstanceFollowRedirects(false);
            // データを書き込む
            con.setDoOutput(true);
            // 時間制限
            con.setReadTimeout(10000);
            con.setConnectTimeout(20000);
            // 接続
            con.connect();
            // データ送信処理
            OutputStream out = null;
            try
            {
                out = con.getOutputStream();
                byte[] word = params[0];
                out.write(word);
                out.flush();
            } 
            catch (IOException e)
            {e.printStackTrace();} 
            finally
            {
                if (out != null)
                {out.close(); }
            }

            final int status = con.getResponseCode();
            if (status == HttpURLConnection.HTTP_OK)
            {
                //レスポンス処理(検索とか)
                final InputStream in = con.getInputStream();
                String encoding = con.getContentEncoding();
                if(encoding == null)
                {encoding="UTF-8";}
                final InputStreamReader inReader = new InputStreamReader(in,encoding);
                final BufferedReader bufReader = new BufferedReader(inReader);

                while ((res = bufReader.readLine()) != null)
                {stbl.append(res);}
                bufReader.close();
                inReader.close();
                in.close();

            }
            else
            {Log.d("接続失敗", String.valueOf(status));}
        }
        catch (IOException e)
        {Log.d("接続失敗", String.valueOf(e)); }
        finally {
            if (con != null) {
                con.disconnect();
            }
        }
        return res;
    }
    //証明書の設定。通信するために必要
    private void registerClientCert(Context context) throws Exception
    {
        KeyManagerFactory keyManagerFactory;
        //証明書作成したときのパス
        final char[] PASSWORD = "***PASSWORD***".toCharArray();
        InputStream inputStream;
        try
        {
            inputStream = context.getResources().getAssets().open("certificate.pfx");
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(inputStream,PASSWORD);
            keyManagerFactory = KeyManagerFactory.getInstance("X509");
            keyManagerFactory.init(keyStore,PASSWORD);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagerFactory.getKeyManagers(),null,null);
            HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        }catch (IOException e)
        {throw new Exception(e);}
        catch (NoSuchAlgorithmException e)
        {throw new Exception(e);}
    }
}
<?php
    //受信したbody部分を取り出す
    $bin = file_get_contents("php://input");
    $path = dirname(__FILE__);

    //日本時間に設定(ファイル名用)
    date_default_timezone_set('Asia/Tokyo');
    $now = microtime(true);
    $ms = (int)($now - ((int)$now) * 1000);
    $msStr = str_pad($ms, 3, 0, STR_PAD_LEFT);
    $dtStr = date("Ymd_His"). "_" . substr(explode(".", (microtime(true) . ""))[1], 0, 3);

    //ファイル名を設定
    $fname = $path . "\\". $dtStr . ".jpg";
    $picname = $dtStr . ".jpg";

    unlink($fname);

    $fp = fopen($fname, 'wb');
    fwrite($fp, $bin);
    fclose($fp);
?>

試したこと

画像を分割し、複数回に分けて送信
→合体時に後に送信した画像が復元されず失敗

タイムアウトの時間を変更
→待ち時間最大まで使用するが、timeoutやconnect pearが発生し失敗

補足情報

AndroidStudioバージョン:3.1.4
スマホのAndroidバージョン:8.0.0

見よう見まねの拙いコードではございますが、
何卒宜しくお願い致します。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正の依頼

  • hoshi-takanori

    2020/02/18 19:24

    サーバーの設定の問題って可能性も…。他のクライアントから巨大なファイル送ったらどうなりますか?

    キャンセル

  • front_sea

    2020/02/21 10:21

    ありがとうございます。
    他のクライアントでも試してみたいと思います。
    後日試した結果を記載いたします。

    キャンセル

  • front_sea

    2020/07/06 15:47

    ようやく別の端末が手に入り試すことができました。
    結果はやはりエラーが発生、エラー内容は以下の通りでした。
    Caused by: java.net.SocketException: socket is closed

    キャンセル

まだ回答がついていません

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

  • ただいまの回答率 89.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる