检索组成员时发生Microsoft Graph 3.0 java.net.SocketTimeoutException

我最近将我的应用程序升级到:

  • Spring Boot:2.4.4
  • Microsoft-graph:3.0.0

升级应用程序时,我遵循了upgrade guide。

我使用以下代码检索组成员:

public void getGroupMembersWithDevices(final IGroup group) {
  List<IUser> users = this.userService.getAllUsersWithDevices();
  List<IUser> groupUsers = new ArrayList<>();
  DirectoryObjectCollectionWithReferencesPage directoryObjectCollectionWithReferencesPage = this.graphClient
    .getGraphServiceClient()
    .groups(group.getGroupId().toString())
    .members()
    .buildRequest()
    .select("Id")
    .top(999)
    .get();

    while (directoryObjectCollectionWithReferencesPage != null) {
      final List<DirectoryObject> directoryObjects = directoryObjectCollectionWithReferencesPage.getCurrentPage();
      List<IUser> usersWithDevices = users.stream().filter(
          one -> directoryObjects.stream().anyMatch(two -> UUID.fromString(two.id).equals(one.getUserId())))
          .collect(Collectors.toList());

      if (usersWithDevices.size() > 0) {
        groupUsers.addAll(usersWithDevices);
        users.removeAll(usersWithDevices);
      }

      final DirectoryObjectCollectionWithReferencesRequestBuilder nextPage = 
        directoryObjectCollectionWithReferencesPage.getNextPage();
      if (nextPage == null) {
        break;
      } else {
        directoryObjectCollectionWithReferencesPage = nextPage.buildRequest().get();
      }
    }

    group.setUsers(groupUsers.stream().collect(Collectors.toSet()));
  }

IUserIDevice是创建的自定义模型。

最终目标是获取设备不属于任何指定组的所有用户。因此,我们首先获取具有设备的所有用户(没有错误),然后对照组成员对其进行交叉检查。

但是,在将应用程序升级到之后,我开始收到错误。我已尝试在生产中重新运行此程序,但在不同的时间间隔不断收到相同的错误。

com.microsoft.graph.core.ClientException: Error executing the request
    at com.microsoft.graph.http.CoreHttpProvider.sendRequestInternal(CoreHttpProvider.java:388)
    at com.microsoft.graph.http.CoreHttpProvider.send(CoreHttpProvider.java:214)
    at com.microsoft.graph.http.CoreHttpProvider.send(CoreHttpProvider.java:191)
    at com.microsoft.graph.http.BaseCollectionRequest.send(BaseCollectionRequest.java:102)
    at com.microsoft.graph.http.BaseEntityCollectionRequest.get(BaseEntityCollectionRequest.java:78)
    at com.app.intune.util.GroupUtil.getGroupMembersWithDevices(GroupUtil.java:128)
    at com.app.intune.ScheduleInventory.scheduleGetInventory(ScheduleInventory.java:85)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:95)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
  Caused by: java.net.SocketTimeoutException: Read timed out
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(Unknown Source)
    at java.net.SocketInputStream.read(Unknown Source)
    at java.net.SocketInputStream.read(Unknown Source)
    at sun.security.ssl.InputRecord.readFully(Unknown Source)
    at sun.security.ssl.InputRecord.read(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readDataRecord(Unknown Source)
    at sun.security.ssl.AppInputStream.read(Unknown Source)
    at okio.Okio$2.read(Okio.java:140)
    at okio.AsyncTimeout$2.read(AsyncTimeout.java:237)
    at okio.RealBufferedSource.indexOf(RealBufferedSource.java:358)
    at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:230)
    at okhttp3.internal.http1.Http1ExchangeCodec.readHeaderLine(Http1ExchangeCodec.java:242)
    at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.java:213)
    at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.java:115)
    at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:94)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:43)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:94)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:88)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at com.microsoft.graph.httpcore.RedirectHandler.intercept(RedirectHandler.java:137)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at com.microsoft.graph.httpcore.RetryHandler.intercept(RetryHandler.java:176)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at com.microsoft.graph.httpcore.AuthenticationHandler.intercept(AuthenticationHandler.java:59)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at com.microsoft.graph.httpcore.TelemetryHandler.intercept(TelemetryHandler.java:69)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
    at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
    at okhttp3.RealCall.execute(RealCall.java:81)
    at com.microsoft.graph.http.CoreHttpProvider.sendRequestInternal(CoreHttpProvider.java:385)

   ... 20 more

更新1:
下面为创建图形客户端和设置代理创建的类:

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.azure.core.http.ProxyOptions;
import com.azure.core.http.netty.NettyAsyncHttpClientBuilder;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.requests.GraphServiceClient;

@Component
public class GraphClient {
    private String clientId;
    private String clientSecret;
    private List<String> scopes;
    private String tenantId;

    @Autowired
    BasicConfiguration configuration;

    @Autowired
    IntuneConfig intuneConfig;

    @PostConstruct
    public void init() {
        this.clientId = this.configuration.getClientId();
        this.clientSecret = this.configuration.getSecretKey();
        this.scopes = Arrays.asList(this.configuration.getScope());
        this.tenantId = this.configuration.getTenant();
    }

    @SuppressWarnings({ "rawtypes" })
    public GraphServiceClient getGraphServiceClient() {
        final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
                    .clientId(this.clientId).clientSecret(this.clientSecret).tenantId(this.tenantId)
                    .httpClient(
                            new NettyAsyncHttpClientBuilder()
                                    .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress(
                                            this.intuneConfig.getProxyHost(), this.intuneConfig.getProxyPort())))
                                    .build())
                    .build();

            final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(this.scopes,
                    clientSecretCredential);

            final GraphServiceClient graphClient = GraphServiceClient.builder()
                    .authenticationProvider(tokenCredentialAuthProvider).buildClient();

            return graphClient;
    }
}
更新2:更新GraphClient类以检索GraphServiceClient实例。该实例仅创建一次。

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.azure.core.http.ProxyOptions;
import com.azure.core.http.netty.NettyAsyncHttpClientBuilder;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.requests.GraphServiceClient;

@Component
public class GraphClient {
    private String clientId;
    private String clientSecret;
    private List<String> scopes;
    private String tenantId;
    
    @SuppressWarnings("rawtypes")
    private static GraphServiceClient graphClient;

    @Autowired
    BasicConfiguration configuration;

    @Autowired
    IntuneConfig intuneConfig;

    @PostConstruct
    public void init() {
        this.clientId = this.configuration.getClientId();
        this.clientSecret = this.configuration.getSecretKey();
        this.scopes = Arrays.asList(this.configuration.getScope());
        this.tenantId = this.configuration.getTenant();
    }

    @SuppressWarnings({ "rawtypes" })
    public GraphServiceClient getGraphServiceClient() {
    if (GraphClient.graphClient == null) {
        final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
                    .clientId(this.clientId).clientSecret(this.clientSecret).tenantId(this.tenantId)
                    .httpClient(
                            new NettyAsyncHttpClientBuilder()
                                    .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress(
                                            this.intuneConfig.getProxyHost(), this.intuneConfig.getProxyPort())))
                                    .build())
                    .build();

            final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(this.scopes,
                    clientSecretCredential);

            final GraphServiceClient client = GraphServiceClient.builder()
                    .authenticationProvider(tokenCredentialAuthProvider).buildClient();

           GraphClient.graphClient = client;
    }
    return GraphClient.graphClient; 
    
    }
}

解决方案

正在为每个调用实例化GraphServiceClient。这又会为每个GraphServiceClient实例化一个OkHttpClient,进而创建一个连接池。由于OS管理连接的方式(它们在一段时间内保持打开,因为关闭和打开连接是一项代价高昂的操作),这将导致计算机上的端口耗尽。
没有更多端口可用后传入的下一个请求,挡路等待端口释放并连接可用,最终超时,出现您看到的异常。

要解决该问题,请执行以下操作:

  • 确保在应用程序的整个生命周期中实例化一次GraphClient类
  • 为getGraphServiceClient方法实现一些延迟加载,以便它在字段中缓存客户端(&q;)并返回该值(如果值不为NULL),而不是为每个调用创建一个新值。

相关文章