You need to enable JavaScript to run this app.
导航

上传回调(Java SDK)

最近更新时间2024.02.04 18:31:06

首次发布时间2023.08.03 16:36:18

上传回调是指客户端在请求时携带回调(Callback)参数,服务端在上传完成后,发送同步的 POST 回调请求到 CallBack 中指定的第三方应用服务器,在服务器确认接受并返回结果后,才将所有结果返回给客户端。
关于上传回调的详细介绍,请参见上传回调

示例代码

从 2.6.0 版本开始,Java SDK 支持在 putObject 和 completeMultipartUpload 接口设置上传回调参数。

普通上传实现上传回调

import com.volcengine.tos.TOSV2;
import com.volcengine.tos.TOSV2ClientBuilder;
import com.volcengine.tos.TosClientException;
import com.volcengine.tos.TosServerException;
import com.volcengine.tos.model.object.PutObjectInput;
import com.volcengine.tos.model.object.PutObjectOutput;

import java.io.ByteArrayInputStream;

public class PutObjectWithCallbackExample {
    public static void main(String[] args) {
        String endpoint = "your endpoint";
        String region = "your region";
        String accessKey = System.getenv("TOS_ACCESS_KEY");
        String secretKey = System.getenv("TOS_SECRET_KEY");

        String bucketName = "bucket-example";
        String objectKey = "example_dir/example_object.txt";

        // 上传回调参数
        String callback = "your callback param";
        // 上传回调自定义变量
        String callbackVar = "your callback custom variable";

        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);

        try{
            // 上传的数据内容,以 String 的形式
            String data = "1234567890abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+<>?,./   :'1234567890abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+<>?,./   :'";
            // 也可以以 byte 数组的形式上传
            byte[] dataInBytes = data.getBytes();
            // 统一封装成 ByteArrayInputStream
            ByteArrayInputStream stream = new ByteArrayInputStream(dataInBytes);
            PutObjectInput putObjectInput = new PutObjectInput()
                    .setBucket(bucketName).setKey(objectKey).setContent(stream)
                    .setCallback(Base64.getEncoder().encodeToString(callback.getBytes(StandardCharsets.UTF_8)))
                    .setCallbackVar(Base64.getEncoder().encodeToString(callbackVar.getBytes(StandardCharsets.UTF_8)));
            PutObjectOutput output = tos.putObject(putObjectInput);
            System.out.println("putObject succeed, object's etag is " + output.getEtag());
            System.out.println("putObject succeed, object's crc64 is " + output.getHashCrc64ecma());
        } catch (TosClientException e) {
            // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送
            System.out.println("putObject failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
        } catch (TosServerException e) {
            // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息
            System.out.println("putObject failed");
            System.out.println("StatusCode: " + e.getStatusCode());
            System.out.println("Code: " + e.getCode());
            System.out.println("Message: " + e.getMessage());
            System.out.println("RequestID: " + e.getRequestID());
        } catch (Throwable t) {
            // 作为兜底捕获其他异常,一般不会执行到这里
            System.out.println("putObject failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
        }
    }
}

分片上传实现上传回调

import com.volcengine.tos.TOSV2;
import com.volcengine.tos.TOSV2ClientBuilder;
import com.volcengine.tos.TosClientException;
import com.volcengine.tos.TosServerException;
import com.volcengine.tos.model.object.CompleteMultipartUploadV2Input;
import com.volcengine.tos.model.object.CompleteMultipartUploadV2Output;
import com.volcengine.tos.model.object.UploadedPartV2;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

public class CompleteMultipartWithCallbackExample {
    public static void main(String[] args) {
        String endpoint = "your endpoint";
        String region = "your region";
        String accessKey = System.getenv("TOS_ACCESS_KEY");
        String secretKey = System.getenv("TOS_SECRET_KEY");

        String bucketName = "bucket-example";
        String objectKey = "example_dir/example_object.txt";

        // 指定的需要合并的分片上传任务的uploadID,
        // 需保证该 uploadId 已通过初始化分片上传接口 createMultipartUpload 调用返回。
        // 否则,对于不存在的 uploadId 会返回 404 not found。
        String uploadId = "the specific uploadId";

        // 上传回调参数
        String callback = "your callback param";
        // 上传回调自定义变量
        String callbackVar = "your callback custom variable";

        // 已上传分片列表
        List<UploadedPartV2> uploadedParts = new ArrayList<>();

        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);

        try {
            CompleteMultipartUploadV2Input complete = new CompleteMultipartUploadV2Input().setBucket(bucketName)
                    .setKey(objectKey).setUploadID(uploadId)
                    .setCallback(Base64.getEncoder().encodeToString(callback.getBytes(StandardCharsets.UTF_8)))
                    .setCallbackVar(Base64.getEncoder().encodeToString(callbackVar.getBytes(StandardCharsets.UTF_8)));
            complete.setUploadedParts(uploadedParts);

            CompleteMultipartUploadV2Output completedOutput = tos.completeMultipartUpload(complete);
            System.out.printf("completeMultipartUpload succeed, etag is %s, crc64 value is %s, location is %s.\n",
                    completedOutput.getEtag(), completedOutput.getHashCrc64ecma(), completedOutput.getLocation());
        } catch (TosClientException e) {
            // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送
            System.out.println("putObject failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
        } catch (TosServerException e) {
            // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息
            System.out.println("putObject failed");
            System.out.println("StatusCode: " + e.getStatusCode());
            System.out.println("Code: " + e.getCode());
            System.out.println("Message: " + e.getMessage());
            System.out.println("RequestID: " + e.getRequestID());
        } catch (Throwable t) {
            // 作为兜底捕获其他异常,一般不会执行到这里
            System.out.println("putObject failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
        }
    }
}

验证回调签名的示例代码

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Map;
import java.util.TreeMap;

public class CallbackHandler {
    private static final String AUTHORIZATION = "Authorization";
    private static final String XTOS_PUBLIC_KEY_URL = "x-tos-pub-key-url";

    public void callbackHandler(HttpServletRequest req, HttpServletResponse resp) {
        byte[] sign = getSignature(req);
        if (sign == null) {
            System.out.println("get callback signature error");
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        byte[] publicKey = getPublicKey(req);
        if (publicKey == null) {
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        byte[] bodyContent;
        byte[] signMd5;
        try {
            Object[] contentAndSignMD5 = getContentAndSignMD5(req);
            signMd5 = (byte[]) contentAndSignMD5[0];
            bodyContent = (byte[]) contentAndSignMD5[1];
        } catch (IOException | NoSuchAlgorithmException e) {
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        try {
            verifySignature(publicKey, signMd5, sign);
            System.out.println(new String(bodyContent));
            resp.setStatus(HttpServletResponse.SC_OK);
            resp.getWriter().write("{\"msg\":\"ok\"}");
        } catch (Exception e) {
            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }

    public byte[] getSignature(HttpServletRequest req) {
        String authorization = req.getHeader(AUTHORIZATION);
        if (authorization == null || authorization.isEmpty()) {
            return null;
        }
        return Base64.getDecoder().decode(authorization);
    }

    public byte[] getPublicKey(HttpServletRequest req) {
        byte[] bytePublicKey = null;
        String publicKeyURLBase64 = req.getHeader(XTOS_PUBLIC_KEY_URL);
        if (publicKeyURLBase64 == null || publicKeyURLBase64.isEmpty()) {
            return bytePublicKey;
        }
        byte[] publicKeyURLBytes = Base64.getDecoder().decode(publicKeyURLBase64);

        try {
            URL url = new URL(new String(publicKeyURLBytes));
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
                throw new IOException("Get public key failed");
            }
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[16384];
            while ((nRead = connection.getInputStream().read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            buffer.flush();
            bytePublicKey = buffer.toByteArray();
            connection.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytePublicKey;
    }

    public Object[] getContentAndSignMD5(HttpServletRequest req) throws IOException, NoSuchAlgorithmException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        byte[] buff = new byte[16384];

        int nRead;
        while ((nRead = req.getInputStream().read(buff, 0, buff.length)) != -1) {
            buffer.write(buff, 0, nRead);
        }
        buffer.flush();
        byte[] bodyContent = buffer.toByteArray();
        String stringToSign = calcStringToSign(req, bodyContent);

        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] signMd5 = md.digest(stringToSign.getBytes());

        return new Object[]{signMd5, bodyContent};
    }

    public String calcStringToSign(HttpServletRequest req, byte[] body) {
        StringBuilder buf = new StringBuilder();
        buf.append(req.getRequestURI());

        Map<String, String[]> query = req.getParameterMap();
        if (!query.isEmpty()) {
            buf.append('?');
            Map<String, String> sorted = new TreeMap<>();
            for (Map.Entry<String, String[]> entry : query.entrySet()) {
                sorted.put(entry.getKey(), entry.getValue()[0]);
            }
            boolean first = true;
            for (Map.Entry<String, String> entry : sorted.entrySet()) {
                if (!first) {
                    buf.append('&');
                }
                buf.append(entry.getKey());
                buf.append('=');
                buf.append(entry.getValue());
                first = false;
            }
        }
        buf.append('\n');
        buf.append(new String(body));
        return buf.toString();
    }

    public void verifySignature(byte[] publicKey, byte[] signMd5, byte[] sign) throws Exception {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(publicKey));

        PublicKey pub = cert.getPublicKey();
        Signature signature = Signature.getInstance("MD5withRSA");
        signature.initVerify(pub);
        signature.update(signMd5);

        if (!signature.verify(sign)) {
            throw new SignatureException("Invalid signature");
        }
    }
}