Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 修复代理模式下请求头没有正确携带的问题 #719

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

hezhengxu2018
Copy link
Collaborator

@hezhengxu2018 hezhengxu2018 commented Oct 22, 2024

proxy时因为一个低级的拼写错误没有正确的携带请求头,导致代理模式时返回的数据不正确。但是现在用户发起的请求中的user-agent和x-forwarded等头部信息也没有正确的携带。虽然影响不大但还是想和跑批时更新的请求做一下区分。

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced error handling and logging for task execution, improving traceability.
  • Improvements

    • Updated HTTP header access method for better alignment with context structure.
    • Clarified logic for manifest retrieval based on file type, ensuring correct API usage.
    • Streamlined cache handling and response generation logic in package management.
    • Improved method visibility and organization within the cache service and controller.

Copy link
Contributor

coderabbitai bot commented Oct 22, 2024

Walkthrough

The changes in the ProxyCacheService, ProxyCacheController, and ShowPackageController classes involve enhancements to error handling, method visibility, and the organization of logic related to manifest retrieval and cache management. The getRewrittenManifest method has been made private, while a new method, replaceTarballUrl, has been introduced. The controller now utilizes a CacheService, simplifying method signatures and altering error handling. Test cases have been updated to reflect these changes, including the removal of tests for certain methods and adjustments to expected outcomes for DELETE operations.

Changes

File Path Change Summary
app/core/service/ProxyCacheService.ts - Updated getPackageManifest and getPackageVersionManifest with error handling.
- Made getRewrittenManifest private and introduced replaceTarballUrl method.
- Updated getProxyResponse to use ctx.headers.
app/port/controller/ProxyCacheController.ts - Updated imports and switched to CacheService.
- Modified refreshProxyCaches and removeProxyCaches methods for cache handling.
- Changed truncateProxyCaches to throw NotImplementedError.
app/port/controller/package/ShowPackageController.ts - Updated show method for cache handling and response construction, including error handling improvements.
test/core/service/ProxyCacheService.test.ts - Removed getRewrittenManifest() tests and updated executeTask() test to focus on getPackageVersionManifest.
test/port/controller/ProxyCacheController/index.test.ts - Removed tests for unauthorized DELETE access and updated expected responses for DELETE operations.

Possibly related PRs

  • feat: proxy mode [sql changed] #571: The changes in ProxyCacheService regarding error handling and method visibility are related to the comprehensive caching mechanism and methods for obtaining package manifests and versions introduced in this PR, which also modifies the ProxyCacheService.

Suggested labels

enhancement, sql

Suggested reviewers

  • elrrrrrrr
  • fengmk2

Poem

In the service where proxies play,
Headers shift in a clever way.
Errors now sing with a clearer tone,
Logging tasks, no longer alone.
With types well-handled, all in line,
Cache updates flow, oh how they shine! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codecov bot commented Oct 22, 2024

Codecov Report

Attention: Patch coverage is 97.50000% with 2 lines in your changes missing coverage. Please review.

Project coverage is 96.47%. Comparing base (d6f0e1d) to head (1f1c9ad).
Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
app/core/service/ProxyCacheService.ts 96.92% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master     #719   +/-   ##
=======================================
  Coverage   96.46%   96.47%           
=======================================
  Files         188      188           
  Lines       18803    18841   +38     
  Branches     2463     2465    +2     
=======================================
+ Hits        18139    18176   +37     
- Misses        664      665    +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@hezhengxu2018 hezhengxu2018 marked this pull request as draft October 23, 2024 01:05
Copy link

sweep-ai bot commented Oct 28, 2024

Hey @hezhengxu2018, here is an example of how you can ask me to improve this pull request:

@Sweep Add unit tests for the new `replaceTarballUrl` method in ProxyCacheService that verify:
1. URL replacement for full package manifests
2. URL replacement for abbreviated package manifests
3. URL replacement for individual version manifests
4. URL replacement for abbreviated version manifests
5. Cases where tarball URLs don't exist in the manifest

📖 For more information on how to use Sweep, please read our documentation.

@hezhengxu2018
Copy link
Collaborator Author

  1. 修复请求上游仓库携带头部信息错误的问题引起的返回manifest异常的问题。
  2. 移除删除单条缓存记录的管理员权限需求,删除缓存和更新缓存一样都是日常的操作不需要有严格的控制。
  3. 将全量删除缓存设为 Not Implemented,原有实现过于粗暴且没有清除redis的缓存,之后计划通过schedule实现。

@hezhengxu2018 hezhengxu2018 marked this pull request as ready for review October 28, 2024 11:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
test/port/controller/ProxyCacheController/index.test.ts (1)

175-179: Update the test description to match the new behavior.

The test description suggests it "should delete all packages" but the implementation now expects a 501 Not Implemented response. Consider renaming the test to something like "should return not implemented status" to better reflect the current behavior.

-    it('should delete all packages about "foo-bar".', async () => {
+    it('should return not implemented status for truncate operation', async () => {
test/core/service/ProxyCacheService.test.ts (1)

Line range hint 1-238: Add tests for header transmission in proxy mode.

Given that the main PR objective is to fix header transmission issues in proxy mode, we should add test cases to verify this functionality.

Would you like me to help generate test cases that verify:

  1. Correct transmission of headers like "user-agent" and "x-forwarded"
  2. Error scenarios when headers are missing or malformed
  3. Integration tests for proxy mode with headers

Example test structure:

describe('proxy mode header handling', () => {
  it('should correctly transmit user-agent header', async () => {
    // Test implementation
  });
  
  it('should correctly transmit x-forwarded header', async () => {
    // Test implementation
  });
  
  it('should handle missing headers gracefully', async () => {
    // Test implementation
  });
});
app/core/service/ProxyCacheService.ts (2)

64-74: Consider adding error logging before cache cleanup.

The error handling for NFS operations is good, but consider logging the error before cleanup for better debugging.

      } catch (error) {
+       this.logger.error('[ProxyCacheService] Failed to read manifest from NFS: %s', error);
        await this.nfsAdapter.remove(cachedStoreKey);
        await this.proxyCacheRepository.removeProxyCache(fullname, fileType);
        throw error;
      }
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 74-74: app/core/service/ProxyCacheService.ts#L74
Added line #L74 was not covered by tests


Line range hint 235-258: Header forwarding implementation looks good.

The changes correctly fix the header forwarding issue mentioned in the PR objectives. Consider adding a type for expected headers.

interface ProxyHeaders {
  accept?: string;
  'user-agent'?: string;
  'x-forwarded-for'?: string;
}
app/port/controller/ProxyCacheController.ts (1)

Line range hint 105-119: Security Concern: Removing Authorization Check from removeProxyCaches

The removal of the @Context() parameter and the associated admin check allows any authenticated user to delete proxy caches for a package. This could lead to unauthorized cache deletions, impacting other users and potentially degrading system performance or reliability. Consider reinstating the authorization check to ensure that only authorized users (e.g., admins) can perform this operation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 4295c42 and 1f1c9ad.

📒 Files selected for processing (5)
  • app/core/service/ProxyCacheService.ts (6 hunks)
  • app/port/controller/ProxyCacheController.ts (7 hunks)
  • app/port/controller/package/ShowPackageController.ts (1 hunks)
  • test/core/service/ProxyCacheService.test.ts (1 hunks)
  • test/port/controller/ProxyCacheController/index.test.ts (2 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
app/core/service/ProxyCacheService.ts

[warning] 74-74: app/core/service/ProxyCacheService.ts#L74
Added line #L74 was not covered by tests


[warning] 107-107: app/core/service/ProxyCacheService.ts#L107
Added line #L107 was not covered by tests

🔇 Additional comments (5)
test/port/controller/ProxyCacheController/index.test.ts (1)

136-141: LGTM! Clear and accurate test assertions.

The assertions correctly verify both the deletion result and the remaining cache state, with helpful comments distinguishing between "foo-bar" and "foobar" related counts.

test/core/service/ProxyCacheService.test.ts (2)

Line range hint 217-225: Verify test coverage after mocking changes.

The change from getUpstreamFullManifests to getPackageVersionManifest reflects architectural changes in how manifests are retrieved. However, we should ensure that the manifest retrieval functionality is still thoroughly tested.

Let's verify the test coverage:

#!/bin/bash
# Description: Check test coverage for manifest retrieval methods
# Look for other tests that might cover the getUpstreamFullManifests functionality

# Search for tests of getUpstreamFullManifests
echo "Searching for getUpstreamFullManifests tests..."
rg -l "getUpstreamFullManifests" "test/"

# Search for additional tests of getPackageVersionManifest
echo "Searching for getPackageVersionManifest tests..."
rg -l "getPackageVersionManifest" "test/"

Line range hint 1-238: Verify coverage for removed getRewrittenManifest tests.

The AI summary indicates that the entire getRewrittenManifest() test suite was removed. Since this method is now private, we need to ensure its functionality is still indirectly tested through public methods.

Let's verify the indirect test coverage:

✅ Verification successful

The getRewrittenManifest functionality is well covered through public methods

The search results show that getRewrittenManifest is still thoroughly tested through its public interfaces:

  • It's tested through getPackageManifest() with both success and cache scenarios
  • It's tested through getPackageVersionManifest() with both direct version and tag resolution
  • The implementation remains in ProxyCacheService.ts as a private method, and its functionality is verified through these public method tests

The removal of direct getRewrittenManifest tests is appropriate since it's now a private implementation detail, and its behavior is properly verified through the public interface tests that remain in the file.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if getRewrittenManifest functionality is still tested
# Look for tests that might indirectly test this functionality

# Search for the implementation of getRewrittenManifest
echo "Searching for getRewrittenManifest implementation..."
ast-grep --pattern 'getRewrittenManifest($$$) {
  $$$
}'

# Search for tests that might cover this functionality
echo "Searching for potential indirect tests..."
rg -l "manifest.*rewrite|rewrite.*manifest" "test/"

Length of output: 318


Script:

#!/bin/bash
# Let's try a different approach to find the implementation and test coverage

# 1. Find the implementation of getRewrittenManifest with more context
echo "Searching for getRewrittenManifest implementation..."
rg -A 5 "getRewrittenManifest" app/

# 2. Look for test files that mock or use this method
echo "Searching for test files using getRewrittenManifest..."
rg -A 5 "getRewrittenManifest" test/

# 3. Look for related test files that might cover manifest rewriting
echo "Searching for related manifest tests..."
rg -l "getPackageManifest|getPackageVersionManifest" test/

Length of output: 5183

app/core/service/ProxyCacheService.ts (1)

126-145: LGTM! Well-structured URL rewriting logic.

Clean implementation with good type safety and clear separation between package and version manifest handling.

app/port/controller/package/ShowPackageController.ts (1)

75-76: Verify that request headers are correctly forwarded in proxy mode

These changes aim to fix the issue where request headers were not properly carried in proxy mode. Please ensure that getProxyResponse correctly forwards the request headers from ctx to the upstream request.

Run the following script to verify that getProxyResponse uses ctx.request.headers when making the upstream request:

✅ Verification successful

Based on the code found, I can now generate the final response:

Headers are properly forwarded in proxy mode, but with specific security considerations

Looking at the implementation of getProxyResponse in ProxyCacheService, the service selectively forwards essential headers:

  • accept header for content negotiation
  • user-agent for client identification
  • Adds secure proxy headers like x-forwarded-for and via
  • Includes proper authorization headers for upstream registry

The implementation follows security best practices by not blindly forwarding all headers but instead explicitly choosing which headers to forward.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that `getProxyResponse` forwards headers from `ctx.request` to the upstream request.

# Test: Search for the usage of `ctx.request.headers` in the `getProxyResponse` method. Expect to find forward of headers.
rg -A 10 -g '*.ts' 'async getProxyResponse' | rg 'ctx\.request\.headers'

Length of output: 74


Script:

#!/bin/bash
# Let's first find the implementation of getProxyResponse method
ast-grep --pattern 'async getProxyResponse($$$) {
  $$$
}'

# Also search for any proxy-related header forwarding in the codebase
rg -g '*.ts' -B 2 -A 5 'headers.*proxy|proxy.*headers'

Length of output: 998


Script:

#!/bin/bash
# Let's find the implementation of ProxyCacheService to see how it handles headers
fd -e ts "ProxyCacheService" --exec cat {}

# Also look for any proxy configuration or header handling in the service
rg -g '*.ts' -B 3 -A 10 'class ProxyCacheService'

Length of output: 21015

Comment on lines +189 to 220
// only used by schedule task
private async getRewrittenManifest<T extends DIST_NAMES>(fullname:string, fileType: T, versionOrTag?:string): Promise<GetSourceManifestAndCacheReturnType<T>> {
let responseResult;
const USER_AGENT = 'npm_service.cnpmjs.org/cnpmcore';
switch (fileType) {
case DIST_NAMES.FULL_MANIFESTS:
responseResult = await this.getUpstreamFullManifests(fullname);
case DIST_NAMES.FULL_MANIFESTS: {
const url = `/${encodeURIComponent(fullname)}?t=${Date.now()}&cache=0`;
responseResult = await this.getProxyResponse({ url, headers: { accept: 'application/json', 'user-agent': USER_AGENT } }, { dataType: 'json' });
break;
case DIST_NAMES.ABBREVIATED_MANIFESTS:
responseResult = await this.getUpstreamAbbreviatedManifests(fullname);
}
case DIST_NAMES.ABBREVIATED_MANIFESTS: {
const url = `/${encodeURIComponent(fullname)}?t=${Date.now()}&cache=0`;
responseResult = await this.getProxyResponse({ url, headers: { accept: ABBREVIATED_META_TYPE, 'user-agent': USER_AGENT } }, { dataType: 'json' });
break;
case DIST_NAMES.MANIFEST:
responseResult = await this.getUpstreamPackageVersionManifest(fullname, versionOrTag!);
}
case DIST_NAMES.MANIFEST: {
const url = `/${encodeURIComponent(fullname)}/${encodeURIComponent(versionOrTag!)}`;
responseResult = await this.getProxyResponse({ url, headers: { accept: 'application/json', 'user-agent': USER_AGENT } }, { dataType: 'json' });
break;
case DIST_NAMES.ABBREVIATED:
responseResult = await this.getUpstreamAbbreviatedPackageVersionManifest(fullname, versionOrTag!);
}
case DIST_NAMES.ABBREVIATED: {
const url = `/${encodeURIComponent(fullname)}/${encodeURIComponent(versionOrTag!)}`;
responseResult = await this.getProxyResponse({ url, headers: { accept: ABBREVIATED_META_TYPE, 'user-agent': USER_AGENT } }, { dataType: 'json' });
break;
}
default:
break;
}

// replace tarball url
const manifest = responseResult.data;
const { sourceRegistry, registry } = this.config.cnpmcore;
if (isPkgManifest(fileType)) {
// pkg manifest
const versionMap = manifest.versions || {};
for (const key in versionMap) {
const versionItem = versionMap[key];
if (versionItem?.dist?.tarball) {
versionItem.dist.tarball = versionItem.dist.tarball.replace(sourceRegistry, registry);
}
}
} else {
// pkg version manifest
const distItem = manifest.dist || {};
if (distItem.tarball) {
distItem.tarball = distItem.tarball.replace(sourceRegistry, registry);
}
}
const manifest = this.replaceTarballUrl(responseResult.data, fileType);
return manifest;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider extracting common URL pattern.

The URL construction is similar across cases. Consider extracting the pattern.

+ private buildProxyUrl(fullname: string, versionOrTag?: string): string {
+   const base = `/${encodeURIComponent(fullname)}`;
+   return versionOrTag 
+     ? `${base}/${encodeURIComponent(versionOrTag)}`
+     : `${base}?t=${Date.now()}&cache=0`;
+ }

Committable suggestion was skipped due to low confidence.

Comment on lines +98 to +107
try {
const nfsBytes = await this.nfsAdapter.getBytes(cachedStoreKey);
const nfsString = Buffer.from(nfsBytes!).toString();
return JSON.parse(nfsString) as PackageJSONType | AbbreviatedPackageJSONType;
} catch (error) {
/* c8 ignore next 3 */
await this.nfsAdapter.remove(cachedStoreKey);
await this.proxyCacheRepository.removeProxyCache(fullname, fileType);
throw error;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider extracting common error handling logic.

This error handling block is duplicated from getPackageManifest. Consider extracting it into a private method.

+ private async handleNFSError(cachedStoreKey: string, fullname: string, fileType: DIST_NAMES) {
+   this.logger.error('[ProxyCacheService] Failed to read manifest from NFS: %s', error);
+   await this.nfsAdapter.remove(cachedStoreKey);
+   await this.proxyCacheRepository.removeProxyCache(fullname, fileType);
+   throw error;
+ }

Committable suggestion was skipped due to low confidence.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 107-107: app/core/service/ProxyCacheService.ts#L107
Added line #L107 was not covered by tests

@hezhengxu2018 hezhengxu2018 changed the title WIP: 修复代理模式下请求头没有正确携带的问题 fix: 修复代理模式下请求头没有正确携带的问题 Oct 30, 2024
const nfsPkgManifgest = JSON.parse(nfsString);
return nfsPkgManifgest;
} catch (error) {
/* c8 ignore next 3 */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

看逻辑如果 getBytes 抛出异常,会直接清空之前存储的信息?

用其他办法来做数据清理?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json信息解析不出来,说明存入的信息就有错误或者文件在nfs上已经删掉了。
每天凌晨三点会去刷新一下已经缓存的依赖,也算是一种清理吧,这里删除是防止依赖还没刷新但是读取已经异常了,清除了就会再次请求上游缓存一遍,不用等每天的刷新。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这种有点危险,不能因为 nfs 异常就删除数据吧。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要将这个逻辑修改一下,要明确知道具体错误才能删除数据

@@ -90,22 +91,18 @@ export class ProxyCacheController extends AbstractController {
);
return task;
});
const tasks = await Promise.all(taskList);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

加一个 pMap 来限制一下并发量?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个接口会查询这个依赖所有缓存的json文件,但是只有manifest 和 abbrivated_manifest会更新,因为具体版本的包发布之后就不会再更改(正常情况下),所以创建的任务数量最多也只有2个。不需要特别控制并发

const nfsPkgManifgest = JSON.parse(nfsString);
return nfsPkgManifgest;
} catch (error) {
/* c8 ignore next 3 */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要将这个逻辑修改一下,要明确知道具体错误才能删除数据

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants