pub源代碼:
https://github.com/dart-lang/pub
pub publish 核心代碼:
OAuth2認(rèn)證
try {
final officialPubServers = {
'https://pub.dev',
// [validateAndNormalizeHostedUrl] normalizes https://pub.dartlang.org
// to https://pub.dev, so we don't need to do allow that here.
// Pub uses oauth2 credentials only for authenticating official pub
// servers for security purposes (to not expose pub.dev access token to
// 3rd party servers).
// For testing publish command we're using mock servers hosted on
// localhost address which is not a known pub server address. So we
// explicitly have to define mock servers as official server to test
// publish command with oauth2 credentials.
if (runningFromTest &&
Platform.environment.containsKey('_PUB_TEST_DEFAULT_HOSTED_URL'))
Platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'],
};
// Using OAuth2 authentication client for the official pub servers
final isOfficialServer = officialPubServers.contains(host.toString());
if (isOfficialServer && !cache.tokenStore.hasCredential(host)) {
// Using OAuth2 authentication client for the official pub servers, when
// we don't have an explicit token from [TokenStore] to use instead.
//
// This allows us to use `dart pub token add` to inject a token for use
// with the official servers.
await oauth2.withClient(cache, (client) {
return _publishUsingClient(packageBytes, client);
});
} else {
// For third party servers using bearer authentication client
await withAuthenticatedClient(cache, host, (client) {
return _publishUsingClient(packageBytes, client);
});
}
} on PubHttpResponseException catch (error) {
var url = error.response.request!.url;
if (Uri.parse(url.origin) == Uri.parse(host.origin)) {
handleJsonError(error.response);
} else {
rethrow;
}
}
上述實(shí)現(xiàn)流程如下:
定義了一個(gè) officialPubServers 變量,表示所有官方的 Pub 倉(cāng)庫(kù)地址。
使用 OAuth2 認(rèn)證客戶(hù)端連接官方的 Pub 倉(cāng)庫(kù)旦部,如果不存在 OAuth2 認(rèn)證信息則通過(guò)命令行工具添加笆檀。
如果不是官方的 Pub 倉(cāng)庫(kù)則使用 bearer 認(rèn)證客戶(hù)端連接已卷。
調(diào)用 _publishUsingClient 函數(shù)來(lái)發(fā)布包孽鸡。
如果發(fā)布失敗犀忱,并且失敗的請(qǐng)求的地址是 Pub 倉(cāng)庫(kù)地址钞它,則通過(guò) handleJsonError 函數(shù)處理錯(cuò)誤拜银。如果不是 Pub 倉(cāng)庫(kù)地址,則重新拋出異常遭垛。
發(fā)布包
try {
await log.progress('Uploading', () async {
/// 1. Initiate upload
final parametersResponse =
await retryForHttp('initiating upload', () async {
final request =
http.Request('GET', host.resolve('api/packages/versions/new'));
request.attachPubApiHeaders();
request.attachMetadataHeaders();
return await client.fetch(request);
});
final parameters = parseJsonResponse(parametersResponse);
/// 2. Upload package
var url = _expectField(parameters, 'url', parametersResponse);
if (url is! String) invalidServerResponse(parametersResponse);
cloudStorageUrl = Uri.parse(url);
final uploadResponse =
await retryForHttp('uploading package', () async {
// TODO(nweiz): Cloud Storage can provide an XML-formatted error. We
// should report that error and exit.
var request = http.MultipartRequest('POST', cloudStorageUrl!);
var fields = _expectField(parameters, 'fields', parametersResponse);
if (fields is! Map) invalidServerResponse(parametersResponse);
fields.forEach((key, value) {
if (value is! String) invalidServerResponse(parametersResponse);
request.fields[key] = value;
});
request.followRedirects = false;
request.files.add(
http.MultipartFile.fromBytes(
'file',
packageBytes,
filename: 'package.tar.gz',
),
);
return await client.fetch(request);
});
/// 3. Finalize publish
var location = uploadResponse.headers['location'];
if (location == null) throw PubHttpResponseException(uploadResponse);
final finalizeResponse =
await retryForHttp('finalizing publish', () async {
final request = http.Request('GET', Uri.parse(location));
request.attachPubApiHeaders();
request.attachMetadataHeaders();
return await client.fetch(request);
});
handleJsonSuccess(finalizeResponse);
});
} on AuthenticationException catch (error) {
var msg = '';
if (error.statusCode == 401) {
msg += '$host package repository requested authentication!\n'
'You can provide credentials using:\n'
' $topLevelProgram pub token add $host\n';
}
if (error.statusCode == 403) {
msg += 'Insufficient permissions to the resource at the $host '
'package repository.\nYou can modify credentials using:\n'
' $topLevelProgram pub token add $host\n';
}
if (error.serverMessage != null) {
msg += '\n${error.serverMessage!}\n';
}
dataError(msg + log.red('Authentication failed!'));
}
上述代碼流程:
Initiate upload:發(fā)送一個(gè) GET 請(qǐng)求到 api/packages/versions/new 這個(gè)接口尼桶,以獲取上傳包的一些參數(shù)。請(qǐng)求會(huì)帶有 pubApiHeaders 和 metadataHeaders 兩個(gè)請(qǐng)求頭锯仪,具體信息根據(jù)代碼未知泵督;
Upload package:使用獲取的參數(shù),通過(guò)發(fā)送一個(gè)帶有多個(gè)部分的 POST 請(qǐng)求庶喜,將包文件上傳到云存儲(chǔ)服務(wù)小腊。在請(qǐng)求中,會(huì)有一些字段久窟,具體內(nèi)容在 fields 字段中秩冈,一般用于描述文件的一些元數(shù)據(jù);
Finalize publish:根據(jù)第二步的響應(yīng)結(jié)果中的 location 字段斥扛,發(fā)送一個(gè) GET 請(qǐng)求以確認(rèn)發(fā)布完成入问。請(qǐng)求同樣帶有 pubApiHeaders 和 metadataHeaders 兩個(gè)請(qǐng)求頭。
上面的代碼中的 retryForHttp 是一個(gè)重試機(jī)制稀颁,在某些情況下芬失,請(qǐng)求失敗時(shí)會(huì)重試多次,以獲得成功的請(qǐng)求匾灶。
最后棱烂,handleJsonSuccess 方法被調(diào)用,該方法可以判斷最終的響應(yīng)結(jié)果是否是有效的 JSON 格式阶女,如果不是颊糜,會(huì)拋出異常。
第二步請(qǐng)求地址:
https://storage.googleapis.com
返回第三步請(qǐng)求地址:
https://pub.dev/api/packages/versions/newUploadFinish?
upload_id=1c23e23c-c08d-4c08-bde4-b40bc58fe3b8
&bucket=dartlang-pub-incoming-packages
&key=tmp/1c23e23c-c08d-4c08-bde4-b40bc58fe3b8&etag=1846afb2360418d23d6c17db2bf90c6d
如果賬號(hào)權(quán)限不一致 則在第三步返回:
xxx@gmail.com
has insufficient permissions to upload new versions to existing package http
.
如果是版本已經(jīng)存在第三步返回:
Version 0.13.27 of package httptest already exists.